From d40e75a4bc4a9020765acf83f5b1b87e533c5fd1 Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Pedron Date: Fri, 26 Dec 2014 15:13:52 +0100 Subject: Rework SSL's verify_fun support Until now, only the legacy form was supported (the one existing in Erlang up-to R13B). The user would use the following RabbitMQ configuration: {ssl_options, [ {verify_fun, {Module, Function}} ]} This is converted to the following ssl option: {ssl_options, [ {verify_fun, fun(Errors) -> Module:Function(Errors) end} ]} Although this form is still supported by Erlang R14B+, this is undocumented and some users complain about RabbitMQ not matching the expected behaviour, according to ssl's documentation. Now, the new form is supported as well. Here's what the user would configure: {ssl_options, [ {verify_fun, {Module, Function, InitialUserState}} ]} or: {ssl_options, [ {verify_fun, {Module, Function}} ]} This is converted to the new form: {ssl_options, [ {verify_fun, {fun(OtpCert, Event, State) -> Module:Function(OtpCert, Event, State) end, InitialUserState }} ]} To determine which form to use, we look at the version of the ssl application: o 4.0.1+: We check if Module:Function/3 is exported and use the new form. If Module:Function/1 is exported, we use the old form. If both are exported, the new form is preferred. If InitialUserState is unspecified, 'none' is used. o Before 4.0.1: We use the legacy form only. If Module can't be loaded or if the expected Function/Arity is not exported, an error is logged and RabbitMQ fails to start. --- src/rabbit_networking.erl | 59 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index d59b22f6..1a288374 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -188,12 +188,21 @@ fix_ssl_options(Config) -> fix_verify_fun(fix_ssl_protocol_versions(Config)). fix_verify_fun(SslOptsConfig) -> + %% Starting with ssl 4.0.1 in Erlang R14B, the verify_fun function + %% takes 3 arguments and returns a tuple. + {ok, SslAppVer} = application:get_key(ssl, vsn), + UseNewVerifyFun = rabbit_misc:version_compare(SslAppVer, "4.0.1", gte), case rabbit_misc:pget(verify_fun, SslOptsConfig) of + {Module, Function, InitialUserState} -> + Fun = make_verify_fun(Module, Function, InitialUserState, + UseNewVerifyFun), + rabbit_misc:pset(verify_fun, Fun, SslOptsConfig); {Module, Function} -> - rabbit_misc:pset(verify_fun, - fun (ErrorList) -> - Module:Function(ErrorList) - end, SslOptsConfig); + Fun = make_verify_fun(Module, Function, none, + UseNewVerifyFun), + rabbit_misc:pset(verify_fun, Fun, SslOptsConfig); + undefined when UseNewVerifyFun -> + SslOptsConfig; undefined -> % unknown_ca errors are silently ignored prior to R14B unless we % supply this verify_fun - remove when at least R14B is required @@ -206,6 +215,48 @@ fix_verify_fun(SslOptsConfig) -> end end. +make_verify_fun(Module, Function, InitialUserState, UseNewVerifyFun) -> + try + %% Preload the module: it is required to use + %% erlang:function_exported/3. + Module:module_info() + catch + _:Exception -> + rabbit_log:error("SSL verify_fun: module ~s missing: ~p~n", + [Module, Exception]), + throw({error, {invalid_verify_fun, missing_module}}) + end, + NewForm = erlang:function_exported(Module, Function, 3), + OldForm = erlang:function_exported(Module, Function, 1), + case {NewForm, OldForm} of + {true, _} when UseNewVerifyFun -> + %% This verify_fun is supported by Erlang R14B+ (ssl + %% 4.0.1 and later). + Fun = fun(OtpCert, Event, UserState) -> + Module:Function(OtpCert, Event, UserState) + end, + {Fun, InitialUserState}; + {_, true} -> + %% This verify_fun is supported by: + %% o Erlang up-to R13B; + %% o Erlang R14B+ for undocumented backward + %% compatibility. + %% + %% InitialUserState is ignored in this case. + fun(ErrorList) -> + Module:Function(ErrorList) + end; + {_, false} when not UseNewVerifyFun -> + rabbit_log:error("SSL verify_fun: ~s:~s/1 form required " + "for Erlang R13B~n", [Module, Function]), + throw({error, {invalid_verify_fun, old_form_required}}); + _ -> + Arity = case UseNewVerifyFun of true -> 3; _ -> 1 end, + rabbit_log:error("SSL verify_fun: no ~s:~s/~b exported~n", + [Module, Function, Arity]), + throw({error, {invalid_verify_fun, function_not_exported}}) + end. + fix_ssl_protocol_versions(Config) -> case application:get_env(rabbit, ssl_allow_poodle_attack) of {ok, true} -> -- cgit v1.2.1