%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2008-2010. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% 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. %% %% %CopyrightEnd% %% %% -module(ssl_test_lib). -include("test_server.hrl"). -include("test_server_line.hrl"). %% Note: This directive should only be used in test suites. -compile(export_all). -record(sslsocket, { fd = nil, pid = nil}). timetrap(Time) -> Mul = try test_server:timetrap_scale_factor() catch _:_ -> 1 end, test_server:timetrap(1000+Time*Mul). %% For now always run locally run_where(_) -> ClientNode = node(), ServerNode = node(), {ok, Host} = rpc:call(ServerNode, inet, gethostname, []), {ClientNode, ServerNode, Host}. run_where(_, ipv6) -> ClientNode = node(), ServerNode = node(), {ok, Host} = rpc:call(ServerNode, inet, gethostname, []), {ClientNode, ServerNode, Host}. node_to_hostip(Node) -> [_ , Host] = string:tokens(atom_to_list(Node), "@"), {ok, Address} = inet:getaddr(Host, inet), Address. start_server(Args) -> Result = spawn_link(?MODULE, run_server, [Args]), receive {listen, up} -> Result end. run_server(Opts) -> Node = proplists:get_value(node, Opts), Port = proplists:get_value(port, Opts), Options = proplists:get_value(options, Opts), Pid = proplists:get_value(from, Opts), test_server:format("ssl:listen(~p, ~p)~n", [Port, Options]), {ok, ListenSocket} = rpc:call(Node, ssl, listen, [Port, Options]), Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), run_server(ListenSocket, Opts). run_server(ListenSocket, Opts) -> AcceptSocket = connect(ListenSocket, Opts), Node = proplists:get_value(node, Opts), Pid = proplists:get_value(from, Opts), {Module, Function, Args} = proplists:get_value(mfa, Opts), test_server:format("Server: apply(~p,~p,~p)~n", [Module, Function, [AcceptSocket | Args]]), case rpc:call(Node, Module, Function, [AcceptSocket | Args]) of no_result_msg -> ok; Msg -> test_server:format("Msg: ~p ~n", [Msg]), Pid ! {self(), Msg} end, receive listen -> run_server(ListenSocket, Opts); close -> ok = rpc:call(Node, ssl, close, [AcceptSocket]) end. %%% To enable to test with s_client -reconnect connect(ListenSocket, Opts) -> Node = proplists:get_value(node, Opts), ReconnectTimes = proplists:get_value(reconnect_times, Opts, 0), AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy), case ReconnectTimes of 0 -> AcceptSocket; _ -> remove_close_msg(ReconnectTimes), AcceptSocket end. connect(_, _, 0, AcceptSocket) -> AcceptSocket; connect(ListenSocket, Node, N, _) -> test_server:format("ssl:transport_accept(~p)~n", [ListenSocket]), {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, [ListenSocket]), test_server:format("ssl:ssl_accept(~p)~n", [AcceptSocket]), ok = rpc:call(Node, ssl, ssl_accept, [AcceptSocket]), connect(ListenSocket, Node, N-1, AcceptSocket). remove_close_msg(0) -> ok; remove_close_msg(ReconnectTimes) -> receive {ssl_closed, _} -> remove_close_msg(ReconnectTimes -1) end. start_client(Args) -> Result = spawn_link(?MODULE, run_client, [Args]), receive connected -> Result end. run_client(Opts) -> Node = proplists:get_value(node, Opts), Host = proplists:get_value(host, Opts), Port = proplists:get_value(port, Opts), Pid = proplists:get_value(from, Opts), Options = proplists:get_value(options, Opts), test_server:format("ssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]), case rpc:call(Node, ssl, connect, [Host, Port, Options]) of {ok, Socket} -> Pid ! connected, test_server:format("Client: connected~n", []), %% In specail cases we want to know the client port, it will %% be indicated by sending {port, 0} in options list! send_selected_port(Pid, proplists:get_value(port, Options), Socket), {Module, Function, Args} = proplists:get_value(mfa, Opts), test_server:format("Client: apply(~p,~p,~p)~n", [Module, Function, [Socket | Args]]), case rpc:call(Node, Module, Function, [Socket | Args]) of no_result_msg -> ok; Msg -> Pid ! {self(), Msg} end, receive close -> ok = rpc:call(Node, ssl, close, [Socket]) end; {error, Reason} -> test_server:format("Client: connection failed: ~p ~n", [Reason]), Pid ! {self(), {error, Reason}} end. close(Pid) -> Pid ! close. check_result(Server, ServerMsg, Client, ClientMsg) -> receive {Server, ServerMsg} -> receive {Client, ClientMsg} -> ok; Unexpected -> Reason = {{expected, {Client, ClientMsg}}, {got, Unexpected}}, test_server:fail(Reason) end; {Client, ClientMsg} -> receive {Server, ServerMsg} -> ok; Unexpected -> Reason = {{expected, {Server, ClientMsg}}, {got, Unexpected}}, test_server:fail(Reason) end; {Port, {data,Debug}} when is_port(Port) -> io:format("openssl ~s~n",[Debug]), check_result(Server, ServerMsg, Client, ClientMsg); Unexpected -> Reason = {{expected, {Client, ClientMsg}}, {expected, {Server, ServerMsg}}, {got, Unexpected}}, test_server:fail(Reason) end. check_result(Pid, Msg) -> receive {Pid, Msg} -> ok; {Port, {data,Debug}} when is_port(Port) -> io:format("openssl ~s~n",[Debug]), check_result(Pid,Msg); Unexpected -> Reason = {{expected, {Pid, Msg}}, {got, Unexpected}}, test_server:fail(Reason) end. check_result_ignore_renegotiation_reject(Pid, Msg) -> receive {Pid, fail_session_fatal_alert_during_renegotiation} -> test_server:comment("Server rejected old renegotiation"), ok; {ssl_error, _, esslconnect} -> test_server:comment("Server rejected old renegotiation"), ok; {Pid, Msg} -> ok; {Port, {data,Debug}} when is_port(Port) -> io:format("openssl ~s~n",[Debug]), check_result(Pid,Msg); Unexpected -> Reason = {{expected, {Pid, Msg}}, {got, Unexpected}}, test_server:fail(Reason) end. wait_for_result(Server, ServerMsg, Client, ClientMsg) -> receive {Server, ServerMsg} -> receive {Client, ClientMsg} -> ok; Unexpected -> Unexpected end; {Client, ClientMsg} -> receive {Server, ServerMsg} -> ok; Unexpected -> Unexpected end; {Port, {data,Debug}} when is_port(Port) -> io:format("openssl ~s~n",[Debug]), wait_for_result(Server, ServerMsg, Client, ClientMsg); Unexpected -> Unexpected end. wait_for_result(Pid, Msg) -> receive {Pid, Msg} -> ok; {Port, {data,Debug}} when is_port(Port) -> io:format("openssl ~s~n",[Debug]), wait_for_result(Pid,Msg); Unexpected -> Unexpected end. cert_options(Config) -> ClientCaCertFile = filename:join([?config(priv_dir, Config), "client", "cacerts.pem"]), ClientCertFile = filename:join([?config(priv_dir, Config), "client", "cert.pem"]), ServerCaCertFile = filename:join([?config(priv_dir, Config), "server", "cacerts.pem"]), ServerCertFile = filename:join([?config(priv_dir, Config), "server", "cert.pem"]), ServerKeyFile = filename:join([?config(priv_dir, Config), "server", "key.pem"]), ClientKeyFile = filename:join([?config(priv_dir, Config), "client", "key.pem"]), ServerKeyCertFile = filename:join([?config(priv_dir, Config), "server", "keycert.pem"]), ClientKeyCertFile = filename:join([?config(priv_dir, Config), "client", "keycert.pem"]), BadCaCertFile = filename:join([?config(priv_dir, Config), "badcacert.pem"]), BadCertFile = filename:join([?config(priv_dir, Config), "badcert.pem"]), BadKeyFile = filename:join([?config(priv_dir, Config), "badkey.pem"]), [{client_opts, [{ssl_imp, new},{reuseaddr, true}]}, {client_verification_opts, [{cacertfile, ClientCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}, {ssl_imp, new}]}, {server_opts, [{ssl_imp, new},{reuseaddr, true}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {server_verification_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {client_kc_opts, [{certfile, ClientKeyCertFile}, {ssl_imp, new}]}, {server_kc_opts, [{ssl_imp, new},{reuseaddr, true}, {certfile, ServerKeyCertFile}]}, {client_bad_ca, [{cacertfile, BadCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}, {ssl_imp, new}]}, {client_bad_cert, [{cacertfile, ClientCaCertFile}, {certfile, BadCertFile}, {keyfile, ClientKeyFile}, {ssl_imp, new}]}, {server_bad_ca, [{ssl_imp, new},{cacertfile, BadCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {server_bad_cert, [{ssl_imp, new},{cacertfile, ServerCaCertFile}, {certfile, BadCertFile}, {keyfile, ServerKeyFile}]}, {server_bad_key, [{ssl_imp, new},{cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, BadKeyFile}]} | Config]. start_upgrade_server(Args) -> Result = spawn_link(?MODULE, run_upgrade_server, [Args]), receive {listen, up} -> Result end. run_upgrade_server(Opts) -> Node = proplists:get_value(node, Opts), Port = proplists:get_value(port, Opts), TimeOut = proplists:get_value(timeout, Opts, infinity), TcpOptions = proplists:get_value(tcp_options, Opts), SslOptions = proplists:get_value(ssl_options, Opts), Pid = proplists:get_value(from, Opts), test_server:format("gen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]), {ok, ListenSocket} = rpc:call(Node, gen_tcp, listen, [Port, TcpOptions]), Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), test_server:format("gen_tcp:accept(~p)~n", [ListenSocket]), {ok, AcceptSocket} = rpc:call(Node, gen_tcp, accept, [ListenSocket]), try {ok, SslAcceptSocket} = case TimeOut of infinity -> test_server:format("ssl:ssl_accept(~p, ~p)~n", [AcceptSocket, SslOptions]), rpc:call(Node, ssl, ssl_accept, [AcceptSocket, SslOptions]); _ -> test_server:format("ssl:ssl_accept(~p, ~p, ~p)~n", [AcceptSocket, SslOptions, TimeOut]), rpc:call(Node, ssl, ssl_accept, [AcceptSocket, SslOptions, TimeOut]) end, {Module, Function, Args} = proplists:get_value(mfa, Opts), Msg = rpc:call(Node, Module, Function, [SslAcceptSocket | Args]), Pid ! {self(), Msg}, receive close -> ok = rpc:call(Node, ssl, close, [SslAcceptSocket]) end catch error:{badmatch, Error} -> Pid ! {self(), Error} end. start_upgrade_client(Args) -> spawn_link(?MODULE, run_upgrade_client, [Args]). run_upgrade_client(Opts) -> Node = proplists:get_value(node, Opts), Host = proplists:get_value(host, Opts), Port = proplists:get_value(port, Opts), Pid = proplists:get_value(from, Opts), TcpOptions = proplists:get_value(tcp_options, Opts), SslOptions = proplists:get_value(ssl_options, Opts), test_server:format("gen_tcp:connect(~p, ~p, ~p)~n", [Host, Port, TcpOptions]), {ok, Socket} = rpc:call(Node, gen_tcp, connect, [Host, Port, TcpOptions]), send_selected_port(Pid, Port, Socket), test_server:format("ssl:connect(~p, ~p)~n", [Socket, SslOptions]), {ok, SslSocket} = rpc:call(Node, ssl, connect, [Socket, SslOptions]), {Module, Function, Args} = proplists:get_value(mfa, Opts), test_server:format("apply(~p, ~p, ~p)~n", [Module, Function, [SslSocket | Args]]), Msg = rpc:call(Node, Module, Function, [SslSocket | Args]), Pid ! {self(), Msg}, receive close -> ok = rpc:call(Node, ssl, close, [SslSocket]) end. start_server_error(Args) -> Result = spawn_link(?MODULE, run_server_error, [Args]), receive {listen, up} -> Result end. run_server_error(Opts) -> Node = proplists:get_value(node, Opts), Port = proplists:get_value(port, Opts), Options = proplists:get_value(options, Opts), Pid = proplists:get_value(from, Opts), test_server:format("ssl:listen(~p, ~p)~n", [Port, Options]), case rpc:call(Node, ssl, listen, [Port, Options]) of {ok, ListenSocket} -> %% To make sure error_client will %% get {error, closed} and not {error, connection_refused} Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), test_server:format("ssl:transport_accept(~p)~n", [ListenSocket]), case rpc:call(Node, ssl, transport_accept, [ListenSocket]) of {error, _} = Error -> Pid ! {self(), Error}; {ok, AcceptSocket} -> test_server:format("ssl:ssl_accept(~p)~n", [AcceptSocket]), Error = rpc:call(Node, ssl, ssl_accept, [AcceptSocket]), Pid ! {self(), Error} end; Error -> %% Not really true but as this is an error test %% this is what we want. Pid ! {listen, up}, Pid ! {self(), Error} end. start_client_error(Args) -> spawn_link(?MODULE, run_client_error, [Args]). run_client_error(Opts) -> Node = proplists:get_value(node, Opts), Host = proplists:get_value(host, Opts), Port = proplists:get_value(port, Opts), Pid = proplists:get_value(from, Opts), Options = proplists:get_value(options, Opts), test_server:format("ssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]), Error = rpc:call(Node, ssl, connect, [Host, Port, Options]), Pid ! {self(), Error}. inet_port(Pid) when is_pid(Pid)-> receive {Pid, {port, Port}} -> Port end; inet_port(Node) -> {Port, Socket} = do_inet_port(Node), rpc:call(Node, gen_tcp, close, [Socket]), Port. do_inet_port(Node) -> {ok, Socket} = rpc:call(Node, gen_tcp, listen, [0, [{reuseaddr, true}]]), {ok, Port} = rpc:call(Node, inet, port, [Socket]), {Port, Socket}. no_result(_) -> no_result_msg. trigger_renegotiate(Socket, [ErlData, N]) -> [{session_id, Id} | _ ] = ssl:session_info(Socket), trigger_renegotiate(Socket, ErlData, N, Id). trigger_renegotiate(Socket, _, 0, Id) -> test_server:sleep(1000), case ssl:session_info(Socket) of [{session_id, Id} | _ ] -> fail_session_not_renegotiated; %% Tests that uses this function will not reuse %% sessions so if we get a new session id the %% renegotiation has succeeded. [{session_id, _} | _ ] -> ok; {error, closed} -> fail_session_fatal_alert_during_renegotiation; {error, timeout} -> fail_timeout end; trigger_renegotiate(Socket, ErlData, N, Id) -> ssl:send(Socket, ErlData), trigger_renegotiate(Socket, ErlData, N-1, Id). send_selected_port(Pid, 0, #sslsocket{} = Socket) -> {ok, {_, NewPort}} = ssl:sockname(Socket), Pid ! {self(), {port, NewPort}}; send_selected_port(Pid, 0, Socket) -> {ok, {_, NewPort}} = inet:sockname(Socket), Pid ! {self(), {port, NewPort}}; send_selected_port(_,_,_) -> ok.