[rabbitmq-discuss] Authenticate client using certificate only
jiri at krutil.com
jiri at krutil.com
Thu Aug 19 09:51:08 BST 2010
> Just take the output of 'hg diff' and post it here or somewhere else
> we can see. Thanks.
Attached is a patch against the default branch. I'm new to Erlang so
please forgive my coding style and feel free to change the code as you
see fit.
Please let me know if this could become official.
Cheers
Jiri
-------------- next part --------------
diff -r a11bf1f62fbb include/rabbit.hrl
--- a/include/rabbit.hrl Wed Aug 18 17:40:19 2010 +0100
+++ b/include/rabbit.hrl Thu Aug 19 09:49:35 2010 +0100
@@ -68,7 +68,7 @@
-record(basic_message, {exchange_name, routing_key, content, guid,
is_persistent}).
--record(ssl_socket, {tcp, ssl}).
+-record(ssl_socket, {tcp, ssl, cn}).
-record(delivery, {mandatory, immediate, txn, sender, message}).
-record(amqp_error, {name, explanation, method = none}).
diff -r a11bf1f62fbb src/rabbit_access_control.erl
--- a/src/rabbit_access_control.erl Wed Aug 18 17:40:19 2010 +0100
+++ b/src/rabbit_access_control.erl Thu Aug 19 09:49:35 2010 +0100
@@ -33,7 +33,7 @@
-include_lib("stdlib/include/qlc.hrl").
-include("rabbit.hrl").
--export([check_login/2, user_pass_login/2,
+-export([check_login/3, user_pass_login/2,
check_vhost_access/2, check_resource_access/3]).
-export([add_user/2, delete_user/1, change_password/2, list_users/0,
lookup_user/1]).
@@ -52,10 +52,11 @@
-type(password() :: binary()).
-type(regexp() :: binary()).
-type(scope() :: binary()).
+-type(socket() :: rabbit_networking:ip_port() | rabbit_types:ssl_socket()).
--spec(check_login/2 ::
- (binary(), binary()) -> rabbit_types:user() |
- rabbit_types:channel_exit()).
+-spec(check_login/3 ::
+ (binary(), binary(), socket()) -> rabbit_types:user() |
+ rabbit_types:channel_exit()).
-spec(user_pass_login/2 ::
(username(), password())
-> rabbit_types:user() | rabbit_types:channel_exit()).
@@ -95,33 +96,54 @@
%% SASL PLAIN, as used by the Qpid Java client and our clients. Also,
%% apparently, by OpenAMQ.
-check_login(<<"PLAIN">>, Response) ->
- [User, Pass] = [list_to_binary(T) ||
- T <- string:tokens(binary_to_list(Response), [0])],
- user_pass_login(User, Pass);
+check_login(<<"PLAIN">>, Response, Sock) ->
+ case is_record(Sock, ssl_socket) andalso Sock#ssl_socket.cn /= none of
+ true ->
+ certificate_login(Sock);
+ false ->
+ [User, Pass] = [list_to_binary(T) ||
+ T <- string:tokens(binary_to_list(Response), [0])],
+ user_pass_login(User, Pass)
+ end;
%% AMQPLAIN, as used by Qpid Python test suite. The 0-8 spec actually
%% defines this as PLAIN, but in 0-9 that definition is gone, instead
%% referring generically to "SASL security mechanism", i.e. the above.
-check_login(<<"AMQPLAIN">>, Response) ->
- LoginTable = rabbit_binary_parser:parse_table(Response),
- case {lists:keysearch(<<"LOGIN">>, 1, LoginTable),
- lists:keysearch(<<"PASSWORD">>, 1, LoginTable)} of
- {{value, {_, longstr, User}},
- {value, {_, longstr, Pass}}} ->
- user_pass_login(User, Pass);
- _ ->
- %% Is this an information leak?
- rabbit_misc:protocol_error(
- access_refused,
- "AMQPPLAIN auth info ~w is missing LOGIN or PASSWORD field",
- [LoginTable])
+check_login(<<"AMQPLAIN">>, Response, Sock) ->
+ case is_record(Sock, ssl_socket) andalso Sock#ssl_socket.cn /= none of
+ true ->
+ certificate_login(Sock);
+ false ->
+ LoginTable = rabbit_binary_parser:parse_table(Response),
+ case {lists:keysearch(<<"LOGIN">>, 1, LoginTable),
+ lists:keysearch(<<"PASSWORD">>, 1, LoginTable)} of
+ {{value, {_, longstr, User}},
+ {value, {_, longstr, Pass}}} ->
+ user_pass_login(User, Pass);
+ _ ->
+ %% Is this an information leak?
+ rabbit_misc:protocol_error(
+ access_refused,
+ "AMQPPLAIN auth info ~w is missing LOGIN or PASSWORD field",
+ [LoginTable])
+ end
end;
-check_login(Mechanism, _Response) ->
+check_login(Mechanism, _Response, _Sock) ->
rabbit_misc:protocol_error(
access_refused, "unsupported authentication mechanism '~s'",
[Mechanism]).
+certificate_login(Sock) ->
+ CN = list_to_binary(Sock#ssl_socket.cn),
+ ?LOGDEBUG("Login with cert (CN=~s)~n", [CN]),
+ case lookup_user(CN) of
+ {ok, U} ->
+ U;
+ {error, not_found} ->
+ rabbit_misc:protocol_error(
+ access_refused, "certificate login refused for Common Name '~s'", [CN])
+ end.
+
user_pass_login(User, Pass) ->
?LOGDEBUG("Login with user ~p pass ~p~n", [User, Pass]),
case lookup_user(User) of
diff -r a11bf1f62fbb src/rabbit_networking.erl
--- a/src/rabbit_networking.erl Wed Aug 18 17:40:19 2010 +0100
+++ b/src/rabbit_networking.erl Thu Aug 19 09:49:35 2010 +0100
@@ -45,6 +45,7 @@
start_client/1, start_ssl_client/2]).
-include("rabbit.hrl").
+-include_lib("public_key/include/OTP-PUB-KEY.hrl").
-include_lib("kernel/include/inet.hrl").
-define(RABBIT_TCP_OPTS, [
@@ -216,19 +217,65 @@
start_client(
Sock,
fun (Sock1) ->
- case catch ssl:ssl_accept(Sock1, SslOpts, ?SSL_TIMEOUT * 1000) of
- {ok, SslSock} ->
- rabbit_log:info("upgraded TCP connection ~p to SSL~n",
- [self()]),
- {ok, #ssl_socket{tcp = Sock1, ssl = SslSock}};
- {error, Reason} ->
- {error, {ssl_upgrade_error, Reason}};
- {'EXIT', Reason} ->
- {error, {ssl_upgrade_failure, Reason}}
+ case catch ssl:ssl_accept(Sock1, SslOpts, ?SSL_TIMEOUT * 1000) of
+ {ok, SslSock} ->
+ % authenticate with certificate or password?
+ case proplists:get_value(authentication, SslOpts, password) of
+ cert ->
+ % retrieve client certificate and extract subject Common Name
+ case ssl:peercert(SslSock, [ssl]) of
+ {ok, Cert} ->
+ case extract_common_name(Cert) of
+ error ->
+ {error, {ssl_upgrade_error,
+ "Could not extract CommonName from client cert"}};
+ CommonName ->
+ rabbit_log:info(
+ "upgraded TCP connection ~p to SSL (CN=~p)~n",
+ [self(), CommonName]),
+ {ok, #ssl_socket{
+ tcp = Sock1, ssl = SslSock, cn = CommonName}}
+ end;
+ {error, Reason} ->
+ {error, {ssl_upgrade_error, Reason}}
+ end;
+ _ ->
+ rabbit_log:info("upgraded TCP connection ~p to SSL~n",
+ [self()]),
+ {ok, #ssl_socket{tcp = Sock1, ssl = SslSock, cn = none}}
+ end;
+ {error, Reason} ->
+ {error, {ssl_upgrade_error, Reason}};
+ {'EXIT', Reason} ->
+ {error, {ssl_upgrade_failure, Reason}}
- end
+ end
end).
+extract_common_name(Cert) ->
+ case is_record(Cert, 'OTPCertificate') of
+ true ->
+ TbsCert = Cert#'OTPCertificate'.tbsCertificate,
+ case is_record(TbsCert, 'OTPTBSCertificate') of
+ true ->
+ Subject = TbsCert#'OTPTBSCertificate'.subject,
+ case Subject of
+ {rdnSequence, Attributes} ->
+ find_attribute(Attributes, ?'id-at-commonName');
+ _ -> error
+ end;
+ false -> error
+ end;
+ false -> error
+ end.
+
+find_attribute([], _) -> error;
+find_attribute([Attr | Tail], Key) ->
+ case Attr of
+ [{'AttributeTypeAndValue', Key, {printableString, Value}}] -> Value;
+ _ -> find_attribute(Tail, Key)
+ end.
+
connections() ->
[Pid || {_, Pid, _, _} <- supervisor:which_children(
rabbit_tcp_client_sup)].
diff -r a11bf1f62fbb src/rabbit_reader.erl
--- a/src/rabbit_reader.erl Wed Aug 18 17:40:19 2010 +0100
+++ b/src/rabbit_reader.erl Thu Aug 19 09:49:35 2010 +0100
@@ -719,7 +719,7 @@
connection = Connection =
#connection{protocol = Protocol},
sock = Sock}) ->
- User = rabbit_access_control:check_login(Mechanism, Response),
+ User = rabbit_access_control:check_login(Mechanism, Response, Sock),
Tune = #'connection.tune'{channel_max = 0,
frame_max = ?FRAME_MAX,
heartbeat = 0},
More information about the rabbitmq-discuss
mailing list