summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlvaro Videla <videlalvaro@gmail.com>2015-09-17 16:53:41 +0300
committerAlvaro Videla <videlalvaro@gmail.com>2015-09-17 16:53:41 +0300
commitc6f2d4f3e9085cfa9f667570e30297e33d354f5f (patch)
treeffee2d0abc262e582799c41ed7c4a74925fa4555 /src
parente8446a21d053445aaa856e300960656dab9ada66 (diff)
parent290861c1796f418ab74b688af860acef4125e375 (diff)
downloadrabbitmq-server-git-c6f2d4f3e9085cfa9f667570e30297e33d354f5f.tar.gz
Merge pull request #310 from rabbitmq/rabbitmq-server-270
Introduce per-user hashing functions, default to SHA-256
Diffstat (limited to 'src')
-rw-r--r--src/rabbit_auth_backend_internal.erl53
-rw-r--r--src/rabbit_password.erl64
-rw-r--r--src/rabbit_password_hashing.erl33
-rw-r--r--src/rabbit_password_hashing_md5.erl28
-rw-r--r--src/rabbit_password_hashing_sha256.erl24
-rw-r--r--src/rabbit_password_hashing_sha512.erl24
-rw-r--r--src/rabbit_upgrade_functions.erl17
7 files changed, 217 insertions, 26 deletions
diff --git a/src/rabbit_auth_backend_internal.erl b/src/rabbit_auth_backend_internal.erl
index ced109d3ff..eaef7cfda5 100644
--- a/src/rabbit_auth_backend_internal.erl
+++ b/src/rabbit_auth_backend_internal.erl
@@ -25,7 +25,7 @@
-export([add_user/2, delete_user/1, lookup_user/1,
change_password/2, clear_password/1,
- hash_password/1, change_password_hash/2,
+ hash_password/2, change_password_hash/2,
set_tags/2, set_permissions/5, clear_permissions/2]).
-export([user_info_keys/0, perms_info_keys/0,
user_perms_info_keys/0, vhost_perms_info_keys/0,
@@ -34,6 +34,9 @@
list_user_permissions/1, list_vhost_permissions/1,
list_user_vhost_permissions/2]).
+%% for testing
+-export([hashing_module_for_user/1]).
+
%%----------------------------------------------------------------------------
-ifdef(use_specs).
@@ -48,7 +51,7 @@
-spec(change_password/2 :: (rabbit_types:username(), rabbit_types:password())
-> 'ok').
-spec(clear_password/1 :: (rabbit_types:username()) -> 'ok').
--spec(hash_password/1 :: (rabbit_types:password())
+-spec(hash_password/2 :: (module(), rabbit_types:password())
-> rabbit_types:password_hash()).
-spec(change_password_hash/2 :: (rabbit_types:username(),
rabbit_types:password_hash()) -> 'ok').
@@ -77,13 +80,22 @@
%%----------------------------------------------------------------------------
%% Implementation of rabbit_auth_backend
+%% Returns a password hashing module for the user record provided. If
+%% there is no information in the record, we consider it to be legacy
+%% (inserted by a version older than 3.6.0) and fall back to MD5, the
+%% now obsolete hashing function.
+hashing_module_for_user(#internal_user{
+ hashing_algorithm = ModOrUndefined}) ->
+ rabbit_password:hashing_mod(ModOrUndefined).
+
user_login_authentication(Username, []) ->
internal_check_user_login(Username, fun(_) -> true end);
user_login_authentication(Username, [{password, Cleartext}]) ->
internal_check_user_login(
Username,
- fun (#internal_user{password_hash = <<Salt:4/binary, Hash/binary>>}) ->
- Hash =:= salted_md5(Salt, Cleartext);
+ fun (#internal_user{password_hash = <<Salt:4/binary, Hash/binary>>} = U) ->
+ Hash =:= rabbit_password:salted_hash(
+ hashing_module_for_user(U), Salt, Cleartext);
(#internal_user{}) ->
false
end);
@@ -147,17 +159,19 @@ permission_index(read) -> #permission.read.
add_user(Username, Password) ->
rabbit_log:info("Creating user '~s'~n", [Username]),
+ %% hash_password will pick the hashing function configured for us
+ %% but we also need to store a hint as part of the record, so we
+ %% retrieve it here one more time
+ HashingMod = rabbit_password:hashing_mod(),
+ User = #internal_user{username = Username,
+ password_hash = hash_password(HashingMod, Password),
+ tags = [],
+ hashing_algorithm = HashingMod},
R = rabbit_misc:execute_mnesia_transaction(
fun () ->
case mnesia:wread({rabbit_user, Username}) of
[] ->
- ok = mnesia:write(
- rabbit_user,
- #internal_user{username = Username,
- password_hash =
- hash_password(Password),
- tags = []},
- write);
+ ok = mnesia:write(rabbit_user, User, write);
_ ->
mnesia:abort({user_already_exists, Username})
end
@@ -191,7 +205,8 @@ lookup_user(Username) ->
change_password(Username, Password) ->
rabbit_log:info("Changing password for '~s'~n", [Username]),
- R = change_password_hash(Username, hash_password(Password)),
+ R = change_password_hash(Username,
+ hash_password(rabbit_password:hashing_mod(), Password)),
rabbit_event:notify(user_password_changed, [{name, Username}]),
R.
@@ -201,14 +216,8 @@ clear_password(Username) ->
rabbit_event:notify(user_password_cleared, [{name, Username}]),
R.
-hash_password(Cleartext) ->
- random:seed(erlang:phash2([node()]),
- time_compat:monotonic_time(),
- time_compat:unique_integer()),
- Salt = random:uniform(16#ffffffff),
- SaltBin = <<Salt:32>>,
- Hash = salted_md5(SaltBin, Cleartext),
- <<SaltBin/binary, Hash/binary>>.
+hash_password(HashingMod, Cleartext) ->
+ rabbit_password:hash(HashingMod, Cleartext).
change_password_hash(Username, PasswordHash) ->
update_user(Username, fun(User) ->
@@ -216,10 +225,6 @@ change_password_hash(Username, PasswordHash) ->
password_hash = PasswordHash }
end).
-salted_md5(Salt, Cleartext) ->
- Salted = <<Salt/binary, Cleartext/binary>>,
- erlang:md5(Salted).
-
set_tags(Username, Tags) ->
rabbit_log:info("Setting user tags for user '~s' to ~p~n",
[Username, Tags]),
diff --git a/src/rabbit_password.erl b/src/rabbit_password.erl
new file mode 100644
index 0000000000..7bc1b28e21
--- /dev/null
+++ b/src/rabbit_password.erl
@@ -0,0 +1,64 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2015 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_password).
+-include("rabbit.hrl").
+
+-define(DEFAULT_HASHING_MODULE, rabbit_password_hashing_sha256).
+
+%%
+%% API
+%%
+
+-export([hash/1, hash/2, generate_salt/0, salted_hash/2, salted_hash/3,
+ hashing_mod/0, hashing_mod/1]).
+
+hash(Cleartext) ->
+ hash(hashing_mod(), Cleartext).
+
+hash(HashingMod, Cleartext) ->
+ SaltBin = generate_salt(),
+ Hash = salted_hash(HashingMod, SaltBin, Cleartext),
+ <<SaltBin/binary, Hash/binary>>.
+
+generate_salt() ->
+ random:seed(erlang:phash2([node()]),
+ time_compat:monotonic_time(),
+ time_compat:unique_integer()),
+ Salt = random:uniform(16#ffffffff),
+ <<Salt:32>>.
+
+salted_hash(Salt, Cleartext) ->
+ salted_hash(hashing_mod(), Salt, Cleartext).
+
+salted_hash(Mod, Salt, Cleartext) ->
+ Fun = fun Mod:hash/1,
+ Fun(<<Salt/binary, Cleartext/binary>>).
+
+hashing_mod() ->
+ rabbit_misc:get_env(rabbit, password_hashing_module,
+ ?DEFAULT_HASHING_MODULE).
+
+hashing_mod(rabbit_password_hashing_sha256) ->
+ rabbit_password_hashing_sha256;
+hashing_mod(rabbit_password_hashing_md5) ->
+ rabbit_password_hashing_md5;
+%% fall back to the hashing function that's been used prior to 3.6.0
+hashing_mod(undefined) ->
+ rabbit_password_hashing_md5;
+%% if a custom module is configured, simply use it
+hashing_mod(CustomMod) when is_atom(CustomMod) ->
+ CustomMod.
diff --git a/src/rabbit_password_hashing.erl b/src/rabbit_password_hashing.erl
new file mode 100644
index 0000000000..3aff2fec4b
--- /dev/null
+++ b/src/rabbit_password_hashing.erl
@@ -0,0 +1,33 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2015 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_password_hashing).
+-include("rabbit.hrl").
+
+-ifdef(use_specs).
+
+-callback hash(rabbit_types:password()) -> rabbit_types:password_hash().
+
+-else.
+
+-export([behaviour_info/1, ]).
+
+behaviour_info(callbacks) ->
+ [{hash, 1}];
+behaviour_info(_Other) ->
+ undefined.
+
+-endif.
diff --git a/src/rabbit_password_hashing_md5.erl b/src/rabbit_password_hashing_md5.erl
new file mode 100644
index 0000000000..7d3e0d80f7
--- /dev/null
+++ b/src/rabbit_password_hashing_md5.erl
@@ -0,0 +1,28 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2015 Pivotal Software, Inc. All rights reserved.
+%%
+
+%% Legacy hashing implementation, only used as a last resort when
+%% #internal_user.hashing_algorithm is md5 or undefined (the case in
+%% pre-3.6.0 user records).
+
+-module(rabbit_password_hashing_md5).
+
+-behaviour(rabbit_password_hashing).
+
+-export([hash/1]).
+
+hash(Binary) ->
+ erlang:md5(Binary).
diff --git a/src/rabbit_password_hashing_sha256.erl b/src/rabbit_password_hashing_sha256.erl
new file mode 100644
index 0000000000..5d230025b5
--- /dev/null
+++ b/src/rabbit_password_hashing_sha256.erl
@@ -0,0 +1,24 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2015 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_password_hashing_sha256).
+
+-behaviour(rabbit_password_hashing).
+
+-export([hash/1]).
+
+hash(Binary) ->
+ crypto:hash(sha256, Binary).
diff --git a/src/rabbit_password_hashing_sha512.erl b/src/rabbit_password_hashing_sha512.erl
new file mode 100644
index 0000000000..50ea22a6d2
--- /dev/null
+++ b/src/rabbit_password_hashing_sha512.erl
@@ -0,0 +1,24 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2015 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_password_hashing_sha512).
+
+-behaviour(rabbit_password_hashing).
+
+-export([hash/1]).
+
+hash(Binary) ->
+ crypto:hash(sha512, Binary).
diff --git a/src/rabbit_upgrade_functions.erl b/src/rabbit_upgrade_functions.erl
index 4eced3f32f..f516737fbc 100644
--- a/src/rabbit_upgrade_functions.erl
+++ b/src/rabbit_upgrade_functions.erl
@@ -51,6 +51,7 @@
-rabbit_upgrade({down_slave_nodes, mnesia, [queue_decorators]}).
-rabbit_upgrade({queue_state, mnesia, [down_slave_nodes]}).
-rabbit_upgrade({recoverable_slaves, mnesia, [queue_state]}).
+-rabbit_upgrade({user_password_hashing, mnesia, [hash_passwords]}).
%% -------------------------------------------------------------------
@@ -84,6 +85,7 @@
-spec(down_slave_nodes/0 :: () -> 'ok').
-spec(queue_state/0 :: () -> 'ok').
-spec(recoverable_slaves/0 :: () -> 'ok').
+-spec(user_password_hashing/0 :: () -> 'ok').
-endif.
@@ -431,6 +433,17 @@ recoverable_slaves(Table) ->
sync_slave_pids, recoverable_slaves, policy, gm_pids, decorators,
state]).
+%% Prior to 3.6.0, passwords were hashed using MD5, this populates
+%% existing records with said default. Users created with 3.6.0+ will
+%% have internal_user.hashing_algorithm populated by the internal
+%% authn backend.
+user_password_hashing() ->
+ transform(
+ rabbit_user,
+ fun ({internal_user, Username, Hash, Tags}) ->
+ {internal_user, Username, Hash, Tags, rabbit_password_hashing_md5}
+ end,
+ [username, password_hash, tags, hashing_algorithm]).
%%--------------------------------------------------------------------
@@ -452,8 +465,8 @@ create(Tab, TabDef) ->
%% Dumb replacement for rabbit_exchange:declare that does not require
%% the exchange type registry or worker pool to be running by dint of
%% not validating anything and assuming the exchange type does not
-%% require serialisation.
-%% NB: this assumes the pre-exchange-scratch-space format
+%% require serialisation. NB: this assumes the
+%% pre-exchange-scratch-space format
declare_exchange(XName, Type) ->
X = {exchange, XName, Type, true, false, false, []},
ok = mnesia:dirty_write(rabbit_durable_exchange, X).