From 4b3a9cbeaa101603b6eaf6d68976e90780d85fc2 Mon Sep 17 00:00:00 2001 From: Magnus Henoch Date: Wed, 3 Feb 2016 18:20:39 +0000 Subject: Allow passing verify_fun for TLS distribution Accept a value of the form {Module, Function, State} from the command line. This is different from the {Fun, State} that ssl:connect etc expect, since there's no clean way to parse a fun from a command line argument. --- lib/ssl/doc/src/ssl_distribution.xml | 9 +++-- lib/ssl/src/ssl_tls_dist_proxy.erl | 18 +++++++++ lib/ssl/test/ssl_dist_SUITE.erl | 75 +++++++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 5 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/doc/src/ssl_distribution.xml b/lib/ssl/doc/src/ssl_distribution.xml index dc04d446b0..db867ea74b 100644 --- a/lib/ssl/doc/src/ssl_distribution.xml +++ b/lib/ssl/doc/src/ssl_distribution.xml @@ -196,6 +196,7 @@ Eshell V5.0 (abort with ^G) password cacertfile verify + verify_fun (write as {Module, Function, InitialUserState}) reuse_sessions secure_renegotiate depth @@ -203,6 +204,10 @@ Eshell V5.0 (abort with ^G) ciphers (use old string format) +

Note that verify_fun needs to be written in a different + form than the corresponding SSL option, since funs are not + accepted on the command line.

+

The server can also take the options dhfile and fail_if_no_peer_cert (also prefixed).

@@ -210,10 +215,6 @@ Eshell V5.0 (abort with ^G) initiates a connection to another node. server_-prefixed options are used when accepting a connection from a remote node.

-

More complex options, such as verify_fun, are currently not - available, but a mechanism to handle such options may be added in - a future release.

-

Raw socket options, such as packet and size must not be specified on the command line.

diff --git a/lib/ssl/src/ssl_tls_dist_proxy.erl b/lib/ssl/src/ssl_tls_dist_proxy.erl index 75562d6fae..33204aa881 100644 --- a/lib/ssl/src/ssl_tls_dist_proxy.erl +++ b/lib/ssl/src/ssl_tls_dist_proxy.erl @@ -396,6 +396,10 @@ ssl_options(server, ["server_verify", Value|T]) -> [{verify, atomize(Value)} | ssl_options(server,T)]; ssl_options(client, ["client_verify", Value|T]) -> [{verify, atomize(Value)} | ssl_options(client,T)]; +ssl_options(server, ["server_verify_fun", Value|T]) -> + [{verify_fun, verify_fun(Value)} | ssl_options(server,T)]; +ssl_options(client, ["client_verify_fun", Value|T]) -> + [{verify_fun, verify_fun(Value)} | ssl_options(client,T)]; ssl_options(server, ["server_reuse_sessions", Value|T]) -> [{reuse_sessions, atomize(Value)} | ssl_options(server,T)]; ssl_options(client, ["client_reuse_sessions", Value|T]) -> @@ -428,6 +432,20 @@ atomize(List) when is_list(List) -> atomize(Atom) when is_atom(Atom) -> Atom. +termify(String) when is_list(String) -> + {ok, Tokens, _} = erl_scan:string(String ++ "."), + {ok, Term} = erl_parse:parse_term(Tokens), + Term. + +verify_fun(Value) -> + case termify(Value) of + {Mod, Func, State} when is_atom(Mod), is_atom(Func) -> + Fun = fun Mod:Func/3, + {Fun, State}; + _ -> + error(malformed_ssl_dist_opt, [Value]) + end. + flush_old_controller(Pid, Socket) -> receive {tcp, Socket, Data} -> diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 00f9ee8e3c..b07be0dcad 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -41,7 +41,8 @@ %%-------------------------------------------------------------------- all() -> [basic, payload, plain_options, plain_verify_options, nodelay_option, - listen_port_options, listen_options, connect_options, use_interface]. + listen_port_options, listen_options, connect_options, use_interface, + verify_fun_fail, verify_fun_pass]. groups() -> []. @@ -418,6 +419,78 @@ use_interface(Config) when is_list(Config) -> stop_ssl_node(NH1), success(Config). +%%-------------------------------------------------------------------- +verify_fun_fail() -> + [{doc,"Test specifying verify_fun with a function that always fails"}]. +verify_fun_fail(Config) when is_list(Config) -> + DistOpts = "-ssl_dist_opt " + "server_verify verify_peer server_verify_fun {ssl_dist_SUITE,verify_fail_always,{}} " + "client_verify verify_peer client_verify_fun {ssl_dist_SUITE,verify_fail_always,{}} ", + + NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), + NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), + Node2 = NH2#node_handle.nodename, + + pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + [] = apply_on_ssl_node(NH1, fun () -> nodes() end), + [] = apply_on_ssl_node(NH2, fun () -> nodes() end), + + %% Check that the function ran on the client node. + [{verify_fail_always_ran, true}] = + apply_on_ssl_node(NH1, fun () -> ets:tab2list(verify_fun_ran) end), + %% On the server node, it wouldn't run, because the server didn't + %% request a certificate from the client. + undefined = + apply_on_ssl_node(NH2, fun () -> ets:info(verify_fun_ran) end), + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). + +verify_fail_always(_Certificate, _Event, _State) -> + %% Create an ETS table, to record the fact that the verify function ran. + ets:new(verify_fun_ran, [public, named_table, {heir, whereis(ssl_tls_dist_proxy), {}}]), + ets:insert(verify_fun_ran, {verify_fail_always_ran, true}), + {fail, bad_certificate}. + +%%-------------------------------------------------------------------- +verify_fun_pass() -> + [{doc,"Test specifying verify_fun with a function that always succeeds"}]. +verify_fun_pass(Config) when is_list(Config) -> + DistOpts = "-ssl_dist_opt " + "server_verify verify_peer server_verify_fun {ssl_dist_SUITE,verify_pass_always,{}} " + "server_fail_if_no_peer_cert true " + "client_verify verify_peer client_verify_fun {ssl_dist_SUITE,verify_pass_always,{}} ", + + NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), + Node1 = NH1#node_handle.nodename, + NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), + Node2 = NH2#node_handle.nodename, + + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), + + %% Check that the function ran on the client node. + [{verify_pass_always_ran, true}] = + apply_on_ssl_node(NH1, fun () -> ets:tab2list(verify_fun_ran) end), + %% Check that it ran on the server node as well. The server + %% requested and verified the client's certificate because we + %% passed fail_if_no_peer_cert. + [{verify_pass_always_ran, true}] = + apply_on_ssl_node(NH2, fun () -> ets:tab2list(verify_fun_ran) end), + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). + +verify_pass_always(_Certificate, _Event, State) -> + %% Create an ETS table, to record the fact that the verify function ran. + ets:new(verify_fun_ran, [public, named_table, {heir, whereis(ssl_tls_dist_proxy), {}}]), + ets:insert(verify_fun_ran, {verify_pass_always_ran, true}), + {valid, State}. %%-------------------------------------------------------------------- %%% Internal functions ----------------------------------------------- -- cgit v1.2.3