diff options
| author | Simon MacMullen <simon@rabbitmq.com> | 2010-11-09 16:05:37 +0000 |
|---|---|---|
| committer | Simon MacMullen <simon@rabbitmq.com> | 2010-11-09 16:05:37 +0000 |
| commit | 289d7561c848de6002838cd699094955e25c156d (patch) | |
| tree | 2fdeeaaea397bced6457c7b5e1ac49d57bffbd40 /src | |
| parent | 97ce437419f70320e12f7e7b318766417d725b14 (diff) | |
| download | rabbitmq-server-git-289d7561c848de6002838cd699094955e25c156d.tar.gz | |
Add our slightly flaky demo challenge-response mechanism, and update APIs etc to cope with it.
Diffstat (limited to 'src')
| -rw-r--r-- | src/rabbit_access_control.erl | 35 | ||||
| -rw-r--r-- | src/rabbit_auth_mechanism_scram_md5.erl | 95 | ||||
| -rw-r--r-- | src/rabbit_reader.erl | 76 |
3 files changed, 158 insertions, 48 deletions
diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index 62653fbe72..8d4e49e555 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -33,7 +33,7 @@ -include_lib("stdlib/include/qlc.hrl"). -include("rabbit.hrl"). --export([auth_mechanisms/1, check_login/2, check_user_pass_login/2, +-export([auth_mechanisms/1, check_user_pass_login/2, make_salt/0, check_vhost_access/2, check_resource_access/3]). -export([add_user/2, delete_user/1, change_password/2, set_admin/1, clear_admin/1, list_users/0, lookup_user/1]). @@ -55,12 +55,10 @@ -type(password_hash() :: binary()). -type(regexp() :: binary()). -spec(auth_mechanisms/1 :: (rabbit_networking:socket()) -> binary()). --spec(check_login/2 :: - (binary(), binary()) -> rabbit_types:user() | - rabbit_types:channel_exit()). -spec(check_user_pass_login/2 :: (username(), password()) -> {'ok', rabbit_types:user()} | 'refused'). +-spec(make_salt/0 :: () -> binary()). -spec(check_vhost_access/2 :: (rabbit_types:user(), rabbit_types:vhost()) -> 'ok' | rabbit_types:channel_exit()). @@ -104,35 +102,6 @@ auth_mechanisms(Sock) -> Mechanism:should_offer(Sock)], list_to_binary(string:join(Mechanisms, " ")). -check_login(MechanismBin, Response) -> - Mechanism = mechanism_to_module(MechanismBin), - State = Mechanism:init(), - case Mechanism:handle_response(Response, State) of - {refused, Username} -> - rabbit_misc:protocol_error( - access_refused, "login refused for user '~s'", [Username]); - {protocol_error, Msg, Args} -> - rabbit_misc:protocol_error(access_refused, Msg, Args); - {ok, User} -> - User - end. - -mechanism_to_module(TypeBin) -> - case rabbit_registry:binary_to_type(TypeBin) of - {error, not_found} -> - rabbit_misc:protocol_error( - command_invalid, "unknown authentication mechanism '~s'", - [TypeBin]); - T -> - case rabbit_registry:lookup_module(auth_mechanism, T) of - {error, not_found} -> rabbit_misc:protocol_error( - command_invalid, - "invalid authentication mechanism '~s'", - [T]); - {ok, Module} -> Module - end - end. - check_user_pass_login(Username, Pass) -> case lookup_user(Username) of {ok, User} -> diff --git a/src/rabbit_auth_mechanism_scram_md5.erl b/src/rabbit_auth_mechanism_scram_md5.erl new file mode 100644 index 0000000000..f8ec6c4ccc --- /dev/null +++ b/src/rabbit_auth_mechanism_scram_md5.erl @@ -0,0 +1,95 @@ +%% 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 Developers of the Original Code are LShift Ltd, +%% Cohesive Financial Technologies LLC, and Rabbit Technologies Ltd. +%% +%% Portions created before 22-Nov-2008 00:00:00 GMT by LShift Ltd, +%% Cohesive Financial Technologies LLC, or Rabbit Technologies Ltd +%% are Copyright (C) 2007-2008 LShift Ltd, Cohesive Financial +%% Technologies LLC, and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd are Copyright (C) 2007-2010 LShift +%% Ltd. Portions created by Cohesive Financial Technologies LLC are +%% Copyright (C) 2007-2010 Cohesive Financial Technologies +%% LLC. Portions created by Rabbit Technologies Ltd are Copyright +%% (C) 2007-2010 Rabbit Technologies Ltd. +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_auth_mechanism_scram_md5). +-include("rabbit.hrl"). + +-behaviour(rabbit_auth_mechanism). + +-export([description/0, should_offer/1, init/0, handle_response/2]). + +-include("rabbit_auth_mechanism_spec.hrl"). + +-rabbit_boot_step({?MODULE, + [{description, "auth mechanism plain"}, + {mfa, {rabbit_registry, register, + [auth_mechanism, <<"RABBIT-SCRAM-MD5">>, + ?MODULE]}}, + {requires, rabbit_registry}, + {enables, kernel_ready}]}). + +-record(state, {username = undefined, salt2 = undefined}). + +%% START-OK: Username +%% SECURE: {Salt1, Salt2} (where Salt1 is the salt from the db and +%% Salt2 differs every time) +%% SECURE-OK: md5(Salt2 ++ md5(Salt1 ++ Password)) + +%% The second salt is there to defend against replay attacks. The +%% first is needed since the passwords are salted in the db. + +%% This is only somewhat improved security over PLAIN (if you can +%% break MD5 you can still replay attack) but it's better than nothing +%% and mostly there to prove the use of SECURE / SECURE-OK frames. + +description() -> + [{name, <<"RABBIT-SCRAM-MD5">>}, + {description, <<"RabbitMQ SCRAM-MD5 authentication mechanism">>}]. + +should_offer(_Sock) -> + true. + +init() -> + #state{}. + +handle_response(Username, State = #state{username = undefined}) -> + case rabbit_access_control:lookup_user(Username) of + {ok, User} -> + <<Salt1:4/binary, _/binary>> = User#user.password_hash, + Salt2 = rabbit_access_control:make_salt(), + {challenge, <<Salt1/binary, Salt2/binary>>, + State#state{username = Username, salt2 = Salt2}}; + {error, not_found} -> + {refused, Username} %% TODO information leak + end; + +handle_response(Response, #state{username = Username, salt2 = Salt2}) -> + case rabbit_access_control:lookup_user(Username) of + {ok, User} -> + <<_:4/binary, Hash/binary>> = User#user.password_hash, + Expected = erlang:md5(<<Salt2/binary, Hash/binary>>), + case Response of + Expected -> {ok, User}; + _ -> {refused, Username} + end; + {error, not_found} -> + {refused, Username} + end. diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 748b537ff2..907a00a8ea 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -60,7 +60,8 @@ -record(v1, {parent, sock, connection, callback, recv_length, recv_ref, connection_state, queue_collector, heartbeater, stats_timer, - channel_sup_sup_pid, start_heartbeat_fun}). + channel_sup_sup_pid, start_heartbeat_fun, auth_mechanism, + auth_state}). -define(STATISTICS_KEYS, [pid, recv_oct, recv_cnt, send_oct, send_cnt, send_pend, state, channels]). @@ -301,7 +302,9 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, stats_timer = rabbit_event:init_stats_timer(), channel_sup_sup_pid = ChannelSupSupPid, - start_heartbeat_fun = StartHeartbeatFun + start_heartbeat_fun = StartHeartbeatFun, + auth_mechanism = none, + auth_state = none }, handshake, 8)) catch @@ -744,19 +747,21 @@ handle_method0(MethodName, FieldsBin, handle_method0(#'connection.start_ok'{mechanism = Mechanism, response = Response, client_properties = ClientProperties}, - State = #v1{connection_state = starting, - connection = Connection = - #connection{protocol = Protocol}, - sock = Sock}) -> - User = rabbit_access_control:check_login(Mechanism, Response), - Tune = #'connection.tune'{channel_max = 0, - frame_max = ?FRAME_MAX, - heartbeat = 0}, - ok = send_on_channel0(Sock, Tune, Protocol), - State#v1{connection_state = tuning, - connection = Connection#connection{ - user = User, - client_properties = ClientProperties}}; + State0 = #v1{connection_state = starting, + connection = Connection}) -> + AuthMechanism = auth_mechanism_to_module(Mechanism), + State = State0#v1{auth_mechanism = AuthMechanism, + auth_state = AuthMechanism:init(), + connection_state = securing, + connection = + Connection#connection{ + client_properties = ClientProperties}}, + auth_phase(Response, State); + +handle_method0(#'connection.secure_ok'{response = Response}, + State = #v1{connection_state = securing}) -> + auth_phase(Response, State); + handle_method0(#'connection.tune_ok'{frame_max = FrameMax, heartbeat = ClientHeartbeat}, State = #v1{connection_state = tuning, @@ -826,6 +831,47 @@ handle_method0(_Method, #v1{connection_state = S}) -> send_on_channel0(Sock, Method, Protocol) -> ok = rabbit_writer:internal_send_command(Sock, 0, Method, Protocol). +auth_mechanism_to_module(TypeBin) -> + case rabbit_registry:binary_to_type(TypeBin) of + {error, not_found} -> + rabbit_misc:protocol_error( + command_invalid, "unknown authentication mechanism '~s'", + [TypeBin]); + T -> + case rabbit_registry:lookup_module(auth_mechanism, T) of + {error, not_found} -> rabbit_misc:protocol_error( + command_invalid, + "invalid authentication mechanism '~s'", + [T]); + {ok, Module} -> Module + end + end. + +auth_phase(Response, + State = #v1{auth_mechanism = AuthMechanism, + auth_state = AuthState, + connection = Connection = + #connection{protocol = Protocol}, + sock = Sock}) -> + case AuthMechanism:handle_response(Response, AuthState) of + {refused, Username} -> + rabbit_misc:protocol_error( + access_refused, "login refused for user '~s'", [Username]); + {protocol_error, Msg, Args} -> + rabbit_misc:protocol_error(access_refused, Msg, Args); + {challenge, Challenge, AuthState1} -> + Secure = #'connection.secure'{challenge = Challenge}, + ok = send_on_channel0(Sock, Secure, Protocol), + State#v1{auth_state = AuthState1}; + {ok, User} -> + Tune = #'connection.tune'{channel_max = 0, + frame_max = ?FRAME_MAX, + heartbeat = 0}, + ok = send_on_channel0(Sock, Tune, Protocol), + State#v1{connection_state = tuning, + connection = Connection#connection{user = User}} + end. + %%-------------------------------------------------------------------------- infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items]. |
