diff options
Diffstat (limited to 'lib/ssl/test')
52 files changed, 15129 insertions, 8149 deletions
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index a10f71a3de..0925c0facc 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -37,12 +37,29 @@ VSN=$(SSL_VSN) MODULES = \ ssl_test_lib \ + ssl_app_env_SUITE\ + ssl_alert_SUITE\ ssl_bench_test_lib \ ssl_dist_test_lib \ - ssl_alpn_handshake_SUITE \ + ssl_api_SUITE\ + tls_api_SUITE\ ssl_basic_SUITE \ ssl_bench_SUITE \ ssl_cipher_SUITE \ + ssl_cipher_suite_SUITE \ + openssl_cipher_suite_SUITE\ + ssl_alpn_SUITE \ + openssl_alpn_SUITE\ + ssl_npn_SUITE \ + openssl_npn_SUITE\ + openssl_sni_SUITE\ + ssl_renegotiate_SUITE\ + openssl_renegotiate_SUITE\ + openssl_reject_SUITE\ + ssl_cert_tests\ + ssl_cert_SUITE\ + openssl_server_cert_SUITE\ + openssl_client_cert_SUITE\ ssl_certificate_verify_SUITE\ ssl_crl_SUITE\ ssl_dist_SUITE \ @@ -50,19 +67,26 @@ MODULES = \ ssl_engine_SUITE\ ssl_handshake_SUITE \ ssl_npn_hello_SUITE \ - ssl_npn_handshake_SUITE \ ssl_packet_SUITE \ ssl_payload_SUITE \ ssl_pem_cache_SUITE \ + ssl_session_SUITE \ ssl_session_cache_SUITE \ - ssl_to_openssl_SUITE \ + openssl_session_SUITE \ ssl_ECC_SUITE \ ssl_ECC_openssl_SUITE \ ssl_ECC\ ssl_upgrade_SUITE\ ssl_sni_SUITE \ - make_certs\ - x509_test + ssl_eqc_SUITE \ + ssl_rfc_5869_SUITE \ + tls_1_3_record_SUITE\ + openssl_tls_1_3_version_SUITE\ + tls_1_3_version_SUITE\ + ssl_socket_SUITE\ + make_certs \ + x509_test \ + inet_crypto_dist ERL_FILES = $(MODULES:%=%.erl) @@ -144,7 +168,7 @@ release_tests_spec: opt $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(HRL_FILES_NEEDED_IN_TEST) $(COVER_FILE) "$(RELSYSDIR)" $(INSTALL_DATA) ssl.spec ssl_bench.spec ssl.cover "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" - @tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -) + @tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -) release_docs_spec: diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl new file mode 100644 index 0000000000..63c19d9438 --- /dev/null +++ b/lib/ssl/test/inet_crypto_dist.erl @@ -0,0 +1,1541 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% ------------------------------------------------------------------------- +%% +%% Module for encrypted Erlang protocol - a minimal encrypted +%% distribution protocol based on only a shared secret +%% and the crypto application +%% +-module(inet_crypto_dist). +-define(DIST_NAME, inet_crypto). +-define(DIST_PROTO, crypto). +-define(DRIVER, inet_tcp). +-define(FAMILY, inet). + +-export([listen/1, accept/1, accept_connection/5, + setup/5, close/1, select/1, is_node_name/1]). + +%% Generalized dist API, for sibling IPv6 module inet6_crypto_dist +-export([gen_listen/2, gen_accept/2, gen_accept_connection/6, + gen_setup/6, gen_close/2, gen_select/2]). + +-export([nodelay/0]). + +%% Debug +%%%-compile(export_all). +-export([dbg/0, test_server/0, test_client/1]). + +-include_lib("kernel/include/net_address.hrl"). +-include_lib("kernel/include/dist.hrl"). +-include_lib("kernel/include/dist_util.hrl"). + +-define(PACKET_SIZE, 65536). +-define(BUFFER_SIZE, (?PACKET_SIZE bsl 4)). + +%% ------------------------------------------------------------------------- + +-record(params, + {socket, + dist_handle, + hmac_algorithm = sha256, + aead_cipher = aes_gcm, + rekey_key, + iv = 12, + key = 16, + tag_len = 16, + rekey_interval = 262144 + }). + +params(Socket) -> + #params{socket = Socket}. + + +-record(key_pair, + {type = ecdh, + %% The curve choice greatly affects setup time, + %% we really want an Edwards curve but that would + %% require a very new openssl version. + %% Twisted brainpool curves (*t1) are faster than + %% non-twisted (*r1), 256 is much faster than 384, + %% and so on... +%%% params = brainpoolP384t1, + params = brainpoolP256t1, + public, + private}). + +-define(KEY_PAIR_LIFE_TIME, 3600000). % 1 hour +-define(KEY_PAIR_LIFE_COUNT, 256). % Number of connection setups + + +%% ------------------------------------------------------------------------- +%% Keep the node's public/private key pair in the process state +%% of a key pair server linked to the acceptor process. +%% Create the key pair the first time it is needed +%% so crypto gets time to start first. +%% + +start_key_pair_server() -> + monitor_dist_proc( + spawn_link( + fun () -> + register(?MODULE, self()), + key_pair_server() + end)). + +key_pair_server() -> + key_pair_server(undefined, undefined, undefined). +%% +key_pair_server(KeyPair) -> + key_pair_server( + KeyPair, + erlang:start_timer(?KEY_PAIR_LIFE_TIME, self(), discard), + ?KEY_PAIR_LIFE_COUNT). +%% +key_pair_server(_KeyPair, Timer, 0) -> + cancel_timer(Timer), + key_pair_server(); +key_pair_server(KeyPair, Timer, Count) -> + receive + {Pid, Tag, get_key_pair} -> + case KeyPair of + undefined -> + KeyPair_1 = generate_key_pair(), + Pid ! {Tag, KeyPair_1}, + key_pair_server(KeyPair_1); + #key_pair{} -> + Pid ! {Tag, KeyPair}, + key_pair_server(KeyPair, Timer, Count - 1) + end; + {Pid, Tag, get_new_key_pair} -> + cancel_timer(Timer), + KeyPair_1 = generate_key_pair(), + Pid ! {Tag, KeyPair_1}, + key_pair_server(KeyPair_1); + {timeout, Timer, discard} when is_reference(Timer) -> + key_pair_server() + end. + +generate_key_pair() -> + #key_pair{type = Type, params = Params} = #key_pair{}, + {Public, Private} = + crypto:generate_key(Type, Params), + #key_pair{public = Public, private = Private}. + +cancel_timer(undefined) -> + ok; +cancel_timer(Timer) -> + case erlang:cancel_timer(Timer) of + false -> + receive + {timeout, Timer, _} -> ok + end; + _RemainingTime -> + ok + end. + +get_key_pair() -> + call_key_pair_server(get_key_pair). + +get_new_key_pair() -> + call_key_pair_server(get_new_key_pair). + +call_key_pair_server(Request) -> + Pid = whereis(?MODULE), + Ref = erlang:monitor(process, Pid), + Pid ! {self(), Ref, Request}, + receive + {Ref, Reply} -> + erlang:demonitor(Ref, [flush]), + Reply; + {'DOWN', Ref, process, Pid, Reason} -> + error(Reason) + end. + +compute_shared_secret( + #key_pair{ + type = PublicKeyType, + params = PublicKeyParams, + private = PrivKey}, PubKey) -> + %% + crypto:compute_key(PublicKeyType, PubKey, PrivKey, PublicKeyParams). + +%% ------------------------------------------------------------------------- +%% Erlang distribution plugin structure explained to myself +%% ------- +%% These are the processes involved in the distribution: +%% * net_kernel +%% * The Acceptor +%% * The Controller | Handshaker | Ticker +%% * The DistCtrl process that may be split into: +%% + The Output controller +%% + The Input controller +%% For the regular inet_tcp_dist distribution module, DistCtrl +%% is not one or two processes, but one port - a gen_tcp socket +%% +%% When the VM is started with the argument "-proto_dist inet_crypto" +%% net_kernel registers the module inet_crypto_dist acli,oams distribution +%% module. net_kernel calls listen/1 to create a listen socket +%% and then accept/1 with the listen socket as argument to spawn +%% the Acceptor process, which is linked to net_kernel. Apparently +%% the listen socket is owned by net_kernel - I wonder if it could +%% be owned by the Acceptor process instead... +%% +%% The Acceptor process calls blocking accept on the listen socket +%% and when an incoming socket is returned it spawns the DistCtrl +%% process a linked to the Acceptor. The ownership of the accepted +%% socket is transferred to the DistCtrl process. +%% A message is sent to net_kernel to inform it that an incoming +%% connection has appeared and the Acceptor awaits a reply from net_kernel. +%% +%% net_kernel then calls accept_connection/5 to spawn the Controller | +%% Handshaker | Ticker process that is linked to net_kernel. +%% The Controller then awaits a message from the Acceptor process. +%% +%% When net_kernel has spawned the Controller it replies with a message +%% to the Acceptor that then calls DistCtrl to changes its links +%% so DistCtrl ends up linked to the Controller and not to the Acceptor. +%% The Acceptor then sends a message to the Controller. The Controller +%% then changes role into the Handshaker creates a #hs_data{} record +%% and calls dist_util:handshake_other_started/1. After this +%% the Acceptor goes back into a blocking accept on the listen socket. +%% +%% For the regular distribution inet_tcp_dist DistCtrl is a gen_tcp socket +%% and when it is a process it also acts as a socket. The #hs_data{} +%% record used by dist_util presents a set of funs that are used +%% by dist_util to perform the distribution handshake. These funs +%% make sure to transfer the handshake messages through the DistCtrl +%% "socket". +%% +%% When the handshake is finished a fun for this purpose in #hs_data{} +%% is called, which tells DistCtrl that it does not need to be prepared +%% for any more #hs_data{} handshake calls. The DistCtrl process in this +%% module then spawns the Input controller process that gets ownership +%% of the connection's gen_tcp socket and changes into {active, N} mode +%% so now it gets all incoming traffic and delivers that to the VM. +%% The original DistCtrl process changes role into the Output controller +%% process and starts asking the VM for outbound messages and transfers +%% them on the connection socket. +%% +%% The Handshaker now changes into the Ticker role, and uses only two +%% functions in the #hs_data{} record; one to get socket statistics +%% and one to send a tick. None of these may block for any reason +%% in particular not for a congested socket since that would destroy +%% connection supervision. +%% +%% +%% For an connection net_kernel calls setup/5 which spawns the +%% Controller process as linked to net_kernel. This Controller process +%% connects to the other node's listen socket and when that is succesful +%% spawns the DistCtrl process as linked to the controller and transfers +%% socket ownership to it. +%% +%% Then the Controller creates the #hs_data{} record and calls +%% dist_util:handshake_we_started/1 which changes the process role +%% into Handshaker. +%% +%% When the distribution handshake is finished the procedure is just +%% as for an incoming connection above. +%% +%% +%% To sum it up. +%% +%% There is an Acceptor process that is linked to net_kernel and +%% informs it when new connections arrive. +%% +%% net_kernel spawns Controllers for incoming and for outgoing connections. +%% these Controllers use the DistCtrl processes to do distribution +%% handshake and after that becomes Tickers that supervise the connection. +%% +%% The Controller | Handshaker | Ticker is linked to net_kernel, and to +%% DistCtrl, one or both. If any of these connection processes would die +%% all others should be killed by the links. Therefore none of them may +%% terminate with reason 'normal'. +%% ------------------------------------------------------------------------- + +-compile({inline, [socket_options/0]}). +socket_options() -> + [binary, {active, false}, {packet, 2}, {nodelay, true}, + {sndbuf, ?BUFFER_SIZE}, {recbuf, ?BUFFER_SIZE}, + {buffer, ?BUFFER_SIZE}]. + +%% ------------------------------------------------------------------------- +%% select/1 is called by net_kernel to ask if this distribution protocol +%% is willing to handle Node +%% + +select(Node) -> + gen_select(Node, ?DRIVER). + +gen_select(Node, Driver) -> + case dist_util:split_node(Node) of + {node, _, Host} -> + case Driver:getaddr(Host) of + {ok, _} -> true; + _ -> false + end; + _ -> + false + end. + +%% ------------------------------------------------------------------------- + +is_node_name(Node) -> + dist_util:is_node_name(Node). + +%% ------------------------------------------------------------------------- +%% Called by net_kernel to create a listen socket for this +%% distribution protocol. This listen socket is used by +%% the Acceptor process. +%% + +listen(Name) -> + gen_listen(Name, ?DRIVER). + +gen_listen(Name, Driver) -> + case inet_tcp_dist:gen_listen(Driver, Name) of + {ok, {Socket, Address, Creation}} -> + inet:setopts(Socket, socket_options()), + {ok, + {Socket, Address#net_address{protocol = ?DIST_PROTO}, Creation}}; + Other -> + Other + end. + +%% ------------------------------------------------------------------------- +%% Called by net_kernel to spawn the Acceptor process that awaits +%% new connection in a blocking accept and informs net_kernel +%% when a new connection has appeared, and starts the DistCtrl +%% "socket" process for the connection. +%% + +accept(Listen) -> + gen_accept(Listen, ?DRIVER). + +gen_accept(Listen, Driver) -> + NetKernel = self(), + %% + %% Spawn Acceptor process + %% + monitor_dist_proc( + spawn_opt( + fun () -> + start_key_pair_server(), + accept_loop(Listen, Driver, NetKernel) + end, + [link, {priority, max}])). + +accept_loop(Listen, Driver, NetKernel) -> + case Driver:accept(trace(Listen)) of + {ok, Socket} -> + wait_for_code_server(), + Timeout = net_kernel:connecttime(), + DistCtrl = start_dist_ctrl(trace(Socket), Timeout), + %% DistCtrl is a "socket" + NetKernel ! + trace({accept, + self(), DistCtrl, Driver:family(), ?DIST_PROTO}), + receive + {NetKernel, controller, Controller} -> + call_dist_ctrl(DistCtrl, {controller, Controller, self()}), + Controller ! {self(), controller, Socket}; + {NetKernel, unsupported_protocol} -> + exit(unsupported_protocol) + end, + accept_loop(Listen, Driver, NetKernel); + AcceptError -> + exit({accept, AcceptError}) + end. + +wait_for_code_server() -> + %% This is an ugly hack. Starting encryption on a connection + %% requires the crypto module to be loaded. Loading the crypto + %% module triggers its on_load function, which calls + %% code:priv_dir/1 to find the directory where its NIF library is. + %% However, distribution is started earlier than the code server, + %% so the code server is not necessarily started yet, and + %% code:priv_dir/1 might fail because of that, if we receive + %% an incoming connection on the distribution port early enough. + %% + %% If the on_load function of a module fails, the module is + %% unloaded, and the function call that triggered loading it fails + %% with 'undef', which is rather confusing. + %% + %% So let's avoid that by waiting for the code server to start. + %% + case whereis(code_server) of + undefined -> + timer:sleep(10), + wait_for_code_server(); + Pid when is_pid(Pid) -> + ok + end. + +%% ------------------------------------------------------------------------- +%% Called by net_kernel when a new connection has appeared, to spawn +%% a Controller process that performs the handshake with the new node, +%% and then becomes the Ticker connection supervisor. +%% ------------------------------------------------------------------------- + +accept_connection(Acceptor, DistCtrl, MyNode, Allowed, SetupTime) -> + gen_accept_connection( + Acceptor, DistCtrl, MyNode, Allowed, SetupTime, ?DRIVER). + +gen_accept_connection( + Acceptor, DistCtrl, MyNode, Allowed, SetupTime, Driver) -> + NetKernel = self(), + %% + %% Spawn Controller/handshaker/ticker process + %% + monitor_dist_proc( + spawn_opt( + fun() -> + do_accept( + Acceptor, DistCtrl, + trace(MyNode), Allowed, SetupTime, Driver, NetKernel) + end, + [link, {priority, max}])). + +do_accept( + Acceptor, DistCtrl, MyNode, Allowed, SetupTime, Driver, NetKernel) -> + %% + receive + {Acceptor, controller, Socket} -> + Timer = dist_util:start_timer(SetupTime), + HSData = + hs_data_common( + NetKernel, MyNode, DistCtrl, Timer, + Socket, Driver:family()), + HSData_1 = + HSData#hs_data{ + this_node = MyNode, + this_flags = 0, + allowed = Allowed}, + dist_util:handshake_other_started(trace(HSData_1)) + end. + +%% ------------------------------------------------------------------------- +%% Called by net_kernel to spawn a Controller process that sets up +%% a new connection to another Erlang node, performs the handshake +%% with the other it, and then becomes the Ticker process +%% that supervises the connection. +%% ------------------------------------------------------------------------- + +setup(Node, Type, MyNode, LongOrShortNames, SetupTime) -> + gen_setup(Node, Type, MyNode, LongOrShortNames, SetupTime, ?DRIVER). + +gen_setup(Node, Type, MyNode, LongOrShortNames, SetupTime, Driver) -> + NetKernel = self(), + %% + %% Spawn Controller/handshaker/ticker process + %% + monitor_dist_proc( + spawn_opt( + setup_fun( + Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel), + [link, {priority, max}])). + +-spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()). +setup_fun( + Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) -> + %% + fun() -> + do_setup( + trace(Node), Type, MyNode, LongOrShortNames, SetupTime, + Driver, NetKernel) + end. + +-spec do_setup(_,_,_,_,_,_,_) -> no_return(). +do_setup( + Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) -> + %% + {Name, Address} = split_node(Driver, Node, LongOrShortNames), + ErlEpmd = net_kernel:epmd_module(), + {ARMod, ARFun} = get_address_resolver(ErlEpmd, Driver), + Timer = trace(dist_util:start_timer(SetupTime)), + case ARMod:ARFun(Name, Address, Driver:family()) of + {ok, Ip, TcpPort, Version} -> + do_setup_connect( + Node, Type, MyNode, Timer, Driver, NetKernel, + Ip, TcpPort, Version); + {ok, Ip} -> + case ErlEpmd:port_please(Name, Ip) of + {port, TcpPort, Version} -> + do_setup_connect( + Node, Type, MyNode, Timer, Driver, NetKernel, + Ip, TcpPort, trace(Version)); + Other -> + _ = trace( + {ErlEpmd, port_please, [Name, Ip], Other}), + ?shutdown(Node) + end; + Other -> + _ = trace( + {ARMod, ARFun, [Name, Address, Driver:family()], + Other}), + ?shutdown(Node) + end. + +-spec do_setup_connect(_,_,_,_,_,_,_,_,_) -> no_return(). + +do_setup_connect( + Node, Type, MyNode, Timer, Driver, NetKernel, + Ip, TcpPort, Version) -> + dist_util:reset_timer(Timer), + ConnectOpts = trace(connect_options(socket_options())), + case Driver:connect(Ip, TcpPort, ConnectOpts) of + {ok, Socket} -> + DistCtrl = + try start_dist_ctrl(Socket, net_kernel:connecttime()) + catch error : {dist_ctrl, _} = DistCtrlError -> + _ = trace(DistCtrlError), + ?shutdown(Node) + end, + %% DistCtrl is a "socket" + HSData = + hs_data_common( + NetKernel, MyNode, DistCtrl, Timer, + Socket, Driver:family()), + HSData_1 = + HSData#hs_data{ + other_node = Node, + this_flags = 0, + other_version = Version, + request_type = Type}, + dist_util:handshake_we_started(trace(HSData_1)); + ConnectError -> + _ = trace( + {Driver, connect, [Ip, TcpPort, ConnectOpts], + ConnectError}), + ?shutdown(Node) + end. + +%% ------------------------------------------------------------------------- +%% close/1 is only called by net_kernel on the socket returned by listen/1. + +close(Socket) -> + gen_close(Socket, ?DRIVER). + +gen_close(Socket, Driver) -> + Driver:close(trace(Socket)). + +%% ------------------------------------------------------------------------- + + +hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) -> + %% Field 'socket' below is set to DistCtrl, which makes + %% the distribution handshake process (ticker) call + %% the funs below with DistCtrl as the S argument. + %% So, S =:= DistCtrl below... + #hs_data{ + kernel_pid = NetKernel, + this_node = MyNode, + socket = DistCtrl, + timer = Timer, + %% + f_send = % -> ok | {error, closed}=>?shutdown() + fun (S, Packet) when S =:= DistCtrl -> + try call_dist_ctrl(S, {send, Packet}) + catch error : {dist_ctrl, Reason} -> + _ = trace(Reason), + {error, closed} + end + end, + f_recv = % -> {ok, List} | Other=>?shutdown() + fun (S, 0, infinity) when S =:= DistCtrl -> + try call_dist_ctrl(S, recv) of + {ok, Bin} when is_binary(Bin) -> + {ok, binary_to_list(Bin)}; + Error -> + Error + catch error : {dist_ctrl, Reason} -> + {error, trace(Reason)} + end + end, + f_setopts_pre_nodeup = + fun (S) when S =:= DistCtrl -> + ok + end, + f_setopts_post_nodeup = + fun (S) when S =:= DistCtrl -> + ok + end, + f_getll = + fun (S) when S =:= DistCtrl -> + {ok, S} %% DistCtrl is the distribution port + end, + f_address = % -> #net_address{} | ?shutdown() + fun (S, Node) when S =:= DistCtrl -> + try call_dist_ctrl(S, peername) of + {ok, Address} -> + case dist_util:split_node(Node) of + {node, _, Host} -> + #net_address{ + address = Address, + host = Host, + protocol = ?DIST_PROTO, + family = Family}; + _ -> + ?shutdown(Node) + end; + Error -> + _ = trace(Error), + ?shutdown(Node) + catch error : {dist_ctrl, Reason} -> + _ = trace(Reason), + ?shutdown(Node) + end + end, + f_handshake_complete = % -> ok | ?shutdown() + fun (S, Node, DistHandle) when S =:= DistCtrl -> + try call_dist_ctrl(S, {handshake_complete, DistHandle}) + catch error : {dist_ctrl, Reason} -> + _ = trace(Reason), + ?shutdown(Node) + end + end, + %% + %% mf_tick/1, mf_getstat/1, mf_setopts/2 and mf_getopts/2 + %% are called by the ticker any time after f_handshake_complete/3 + %% so they may not block the caller even for congested socket + mf_tick = + fun (S) when S =:= DistCtrl -> + S ! dist_tick + end, + mf_getstat = % -> {ok, RecvCnt, SendCnt, SendPend} | Other=>ignore_it + fun (S) when S =:= DistCtrl -> + case + inet:getstat(Socket, [recv_cnt, send_cnt, send_pend]) + of + {ok, Stat} -> + split_stat(Stat, 0, 0, 0); + Error -> + trace(Error) + end + end, + mf_setopts = + fun (S, Opts) when S =:= DistCtrl -> + inet:setopts(Socket, setopts_filter(Opts)) + end, + mf_getopts = + fun (S, Opts) when S =:= DistCtrl -> + inet:getopts(Socket, Opts) + end}. + +setopts_filter(Opts) -> + [Opt || + Opt <- Opts, + case Opt of + {K, _} when K =:= active; K =:= deliver; K =:= packet -> false; + K when K =:= list; K =:= binary -> false; + K when K =:= inet; K =:= inet6 -> false; + _ -> true + end]. + +split_stat([{recv_cnt, R}|Stat], _, W, P) -> + split_stat(Stat, R, W, P); +split_stat([{send_cnt, W}|Stat], R, _, P) -> + split_stat(Stat, R, W, P); +split_stat([{send_pend, P}|Stat], R, W, _) -> + split_stat(Stat, R, W, P); +split_stat([], R, W, P) -> + {ok, R, W, P}. + +%% ------------------------------------------------------------ +%% Determine if EPMD module supports address resolving. Default +%% is to use inet_tcp:getaddr/2. +%% ------------------------------------------------------------ +get_address_resolver(EpmdModule, _Driver) -> + case erlang:function_exported(EpmdModule, address_please, 3) of + true -> {EpmdModule, address_please}; + _ -> {erl_epmd, address_please} + end. + + +%% If Node is illegal terminate the connection setup!! +split_node(Driver, Node, LongOrShortNames) -> + case dist_util:split_node(Node) of + {node, Name, Host} -> + check_node(Driver, Node, Name, Host, LongOrShortNames); + {host, _} -> + error_logger:error_msg( + "** Nodename ~p illegal, no '@' character **~n", + [Node]), + ?shutdown2(Node, trace({illegal_node_n@me, Node})); + _ -> + error_logger:error_msg( + "** Nodename ~p illegal **~n", [Node]), + ?shutdown2(Node, trace({illegal_node_name, Node})) + end. + +check_node(Driver, Node, Name, Host, LongOrShortNames) -> + case string:split(Host, ".", all) of + [_] when LongOrShortNames =:= longnames -> + case Driver:parse_address(Host) of + {ok, _} -> + {Name, Host}; + _ -> + error_logger:error_msg( + "** System running to use " + "fully qualified hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown2(Node, trace({not_longnames, Host})) + end; + [_, _|_] when LongOrShortNames =:= shortnames -> + error_logger:error_msg( + "** System NOT running to use " + "fully qualified hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown2(Node, trace({not_shortnames, Host})); + _ -> + {Name, Host} + end. + +%% ------------------------------------------------------------------------- + +connect_options(Opts) -> + case application:get_env(kernel, inet_dist_connect_options) of + {ok, ConnectOpts} -> + Opts ++ setopts_filter(ConnectOpts); + _ -> + Opts + end. + +%% we may not always want the nodelay behaviour +%% for performance reasons +nodelay() -> + case application:get_env(kernel, dist_nodelay) of + undefined -> + {nodelay, true}; + {ok, true} -> + {nodelay, true}; + {ok, false} -> + {nodelay, false}; + _ -> + {nodelay, true} + end. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% The DistCtrl process(es). +%% +%% At net_kernel handshake_complete spawns off the input controller that +%% takes over the socket ownership, and itself becomes the output controller +%% +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%% XXX Missing to "productified": +%%% * Cryptoanalysis by experts, this is crypto amateur work. +%%% * Is it useful over inet_tls_dist; i.e to not have to bother +%%% with certificates but instead manage a secret cluster cookie? +%%% * An application to belong to (kernel) +%%% * Restart and/or code reload policy (not needed in kernel) +%%% * Fitting into the epmd/Erlang distro protocol version framework +%%% (something needs to be created for multiple protocols, epmd, +%%% multiple address families, fallback to previous version, etc) + + +%% Debug client and server + +test_server() -> + {ok, Listen} = gen_tcp:listen(0, socket_options()), + {ok, Port} = inet:port(Listen), + io:format(?MODULE_STRING":test_client(~w).~n", [Port]), + {ok, Socket} = gen_tcp:accept(Listen), + test(Socket). + +test_client(Port) -> + {ok, Socket} = gen_tcp:connect(localhost, Port, socket_options()), + test(Socket). + +test(Socket) -> + start_dist_ctrl(Socket, 10000). + +%% ------------------------------------------------------------------------- + +start_dist_ctrl(Socket, Timeout) -> + Secret = atom_to_binary(auth:get_cookie(), latin1), + Controller = self(), + Server = + monitor_dist_proc( + spawn_opt( + fun () -> + receive + {?MODULE, From, start} -> + {SendParams, RecvParams} = + init(Socket, Secret), + reply(From, self()), + handshake(SendParams, 1, RecvParams, 1, Controller) + end + end, + [link, + {priority, max}, + {message_queue_data, off_heap}, + {fullsweep_after, 0}])), + ok = gen_tcp:controlling_process(Socket, Server), + call_dist_ctrl(Server, start, Timeout). + + +call_dist_ctrl(Server, Msg) -> + call_dist_ctrl(Server, Msg, infinity). +%% +call_dist_ctrl(Server, Msg, Timeout) -> + Ref = erlang:monitor(process, Server), + Server ! {?MODULE, {Ref, self()}, Msg}, + receive + {Ref, Res} -> + erlang:demonitor(Ref, [flush]), + Res; + {'DOWN', Ref, process, Server, Reason} -> + error({dist_ctrl, Reason}) + after Timeout -> % Timeout < infinity is only used by start_dist_ctrl/2 + receive + {'DOWN', Ref, process, Server, _} -> + receive {Ref, _} -> ok after 0 -> ok end, + error({dist_ctrl, timeout}) + %% Server will be killed by link + end + end. + +reply({Ref, Pid}, Msg) -> + Pid ! {Ref, Msg}, + ok. + +%% ------------------------------------------------------------------------- + +-define(TCP_ACTIVE, 16). +-define(CHUNK_SIZE, (?PACKET_SIZE - 512)). + +-define(HANDSHAKE_CHUNK, 1). +-define(DATA_CHUNK, 2). +-define(TICK_CHUNK, 3). +-define(REKEY_CHUNK, 4). + +%% ------------------------------------------------------------------------- +%% Crypto strategy +%% ------- +%% The crypto strategy is as simple as possible to get an encrypted +%% connection as benchmark reference. It is geared around AEAD +%% ciphers in particular AES-GCM. +%% +%% The init message and the start message must fit in the TCP buffers +%% since both sides start with sending the init message, waits +%% for the other end's init message, sends the start message +%% and waits for the other end's start message. So if the send +%% blocks we have a deadlock. +%% +%% The init + start sequence tries to implement Password Encrypted +%% Key Exchange using a node public/private key pair and the +%% shared secret (the Cookie) to create session encryption keys +%% that can not be re-created if the shared secret is compromized, +%% which should create forward secrecy. You need both nodes' +%% key pairs and the shared secret to decrypt the traffic +%% between the nodes. +%% +%% All exchanged messages uses {packet, 2} i.e 16 bit size header. +%% +%% The init message contains a random number and encrypted: the public key +%% and two random numbers. The encryption is done with Key and IV hashed +%% from the unencrypted random number and the shared secret. +%% +%% The other node's public key is used with the own node's private +%% key to create a shared key that is hashed with one of the encrypted +%% random numbers from each side to create Key and IV for the session. +%% +%% The start message contains the two encrypted random numbers +%% this time encrypted with the session keys for verification +%% by the other side, plus the rekey interval. The rekey interval +%% is just there to get an early check for if the other side's +%% maximum rekey interal is acceptable, it is just an embryo +%% of some better check. Any side may rekey earlier but if the +%% rekey interval is exceeded the connection fails. +%% +%% Subsequent encrypted messages has the sequence number and the length +%% of the message as AAD data, and an incrementing IV. These messages +%% has got a message type that differentiates data from ticks and rekeys. +%% Ticks have a random size in an attempt to make them less obvious to spot. +%% +%% Rekeying is done by the sender that creates a new key pair and +%% a new shared secret from the other end's public key and with +%% this and the current key and iv hashes a new key and iv. +%% The new public key is sent to the other end that uses it +%% and its old private key to create the same new shared +%% secret and from that a new key and iv. +%% So the receiver keeps its private key, and the sender keeps +%% the receivers public key for the connection's life time. +%% While the sender generates a new key pair at every rekey, +%% which changes the shared secret at every rekey. +%% +%% The only reaction to errors is to crash noisily (?) wich will bring +%% down the connection and hopefully produce something useful +%% in the local log, but all the other end sees is a closed connection. +%% ------------------------------------------------------------------------- + +init(Socket, Secret) -> + #key_pair{public = PubKey} = KeyPair = get_key_pair(), + Params = params(Socket), + {R2, R3, Msg} = init_msg(Params, PubKey, Secret), + ok = gen_tcp:send(Socket, Msg), + init_recv(Params, Secret, KeyPair, R2, R3). + +init_recv( + #params{socket = Socket, iv = IVLen} = Params, Secret, KeyPair, R2, R3) -> + %% + {ok, InitMsg} = gen_tcp:recv(Socket, 0), + IVSaltLen = IVLen - 6, + try + case init_msg(Params, Secret, KeyPair, R2, R3, InitMsg) of + {#params{iv = <<IV2ASalt:IVSaltLen/binary, IV2ANo:48>>} = + SendParams, + RecvParams, SendStartMsg} -> + ok = gen_tcp:send(Socket, SendStartMsg), + {ok, RecvStartMsg} = gen_tcp:recv(Socket, 0), + #params{ + iv = <<IV2BSalt:IVSaltLen/binary, IV2BNo:48>>} = + RecvParams_1 = + start_msg(RecvParams, R2, R3, RecvStartMsg), + {SendParams#params{iv = {IV2ASalt, IV2ANo}}, + RecvParams_1#params{iv = {IV2BSalt, IV2BNo}}} + end + catch + error : Reason : Stacktrace-> + _ = trace({Reason, Stacktrace}), + exit(connection_closed) + end. + + + +init_msg( + #params{ + hmac_algorithm = HmacAlgo, + aead_cipher = AeadCipher, + key = KeyLen, + iv = IVLen, + tag_len = TagLen}, PubKeyA, Secret) -> + %% + RLen = KeyLen + IVLen, + <<R1A:RLen/binary, R2A:RLen/binary, R3A:RLen/binary>> = + crypto:strong_rand_bytes(3 * RLen), + {Key1A, IV1A} = hmac_key_iv(HmacAlgo, R1A, Secret, KeyLen, IVLen), + Plaintext = [R2A, R3A, PubKeyA], + MsgLen = byte_size(R1A) + TagLen + iolist_size(Plaintext), + AAD = [<<MsgLen:32>>, R1A], + {Ciphertext, Tag} = + crypto:block_encrypt(AeadCipher, Key1A, IV1A, {AAD, Plaintext, TagLen}), + Msg = [R1A, Tag, Ciphertext], + {R2A, R3A, Msg}. +%% +init_msg( + #params{ + hmac_algorithm = HmacAlgo, + aead_cipher = AeadCipher, + key = KeyLen, + iv = IVLen, + tag_len = TagLen, + rekey_interval = RekeyInterval} = Params, + Secret, KeyPair, R2A, R3A, Msg) -> + %% + RLen = KeyLen + IVLen, + case Msg of + <<R1B:RLen/binary, Tag:TagLen/binary, Ciphertext/binary>> -> + {Key1B, IV1B} = hmac_key_iv(HmacAlgo, R1B, Secret, KeyLen, IVLen), + MsgLen = byte_size(Msg), + AAD = [<<MsgLen:32>>, R1B], + case + crypto:block_decrypt( + AeadCipher, Key1B, IV1B, {AAD, Ciphertext, Tag}) + of + <<R2B:RLen/binary, R3B:RLen/binary, PubKeyB/binary>> -> + SharedSecret = compute_shared_secret(KeyPair, PubKeyB), + %% + {Key2A, IV2A} = + hmac_key_iv( + HmacAlgo, SharedSecret, [R2A, R3B], KeyLen, IVLen), + SendParams = + Params#params{ + rekey_key = PubKeyB, + key = Key2A, iv = IV2A}, + %% + StartCleartext = [R2B, R3B, <<RekeyInterval:32>>], + StartMsgLen = TagLen + iolist_size(StartCleartext), + StartAAD = <<StartMsgLen:32>>, + {StartCiphertext, StartTag} = + crypto:block_encrypt( + AeadCipher, Key2A, IV2A, + {StartAAD, StartCleartext, TagLen}), + StartMsg = [StartTag, StartCiphertext], + %% + {Key2B, IV2B} = + hmac_key_iv( + HmacAlgo, SharedSecret, [R2B, R3A], KeyLen, IVLen), + RecvParams = + Params#params{ + rekey_key = KeyPair, + key = Key2B, iv = IV2B}, + %% + {SendParams, RecvParams, StartMsg} + end + end. + +start_msg( + #params{ + aead_cipher = AeadCipher, + key = Key2B, + iv = IV2B, + tag_len = TagLen, + rekey_interval = RekeyIntervalA} = RecvParams, R2A, R3A, Msg) -> + %% + case Msg of + <<Tag:TagLen/binary, Ciphertext/binary>> -> + KeyLen = byte_size(Key2B), + IVLen = byte_size(IV2B), + RLen = KeyLen + IVLen, + MsgLen = byte_size(Msg), + AAD = <<MsgLen:32>>, + case + crypto:block_decrypt( + AeadCipher, Key2B, IV2B, {AAD, Ciphertext, Tag}) + of + <<R2A:RLen/binary, R3A:RLen/binary, RekeyIntervalB:32>> + when RekeyIntervalA =< (RekeyIntervalB bsl 2), + RekeyIntervalB =< (RekeyIntervalA bsl 2) -> + RecvParams#params{rekey_interval = RekeyIntervalB} + end + end. + +hmac_key_iv(HmacAlgo, MacKey, Data, KeyLen, IVLen) -> + <<Key:KeyLen/binary, IV:IVLen/binary>> = + crypto:hmac(HmacAlgo, MacKey, Data, KeyLen + IVLen), + {Key, IV}. + +%% ------------------------------------------------------------------------- +%% net_kernel distribution handshake in progress +%% + +handshake( + SendParams, SendSeq, + #params{socket = Socket} = RecvParams, RecvSeq, Controller) -> + receive + {?MODULE, From, {controller, Controller_1, Parent}} -> + Result = link(Controller_1), + true = unlink(Parent), + reply(From, Result), + handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller_1); + {?MODULE, From, {handshake_complete, DistHandle}} -> + InputHandler = + monitor_dist_proc( + spawn_opt( + fun () -> + link(Controller), + receive + DistHandle -> + ok = + inet:setopts( + Socket, + [{active, ?TCP_ACTIVE}, + nodelay()]), + input_handler( + RecvParams#params{ + dist_handle = DistHandle}, + RecvSeq, empty_q(), infinity) + end + end, + [link, + {priority, normal}, + {message_queue_data, off_heap}, + {fullsweep_after, 0}])), + _ = monitor(process, InputHandler), % For the benchmark test + ok = gen_tcp:controlling_process(Socket, InputHandler), + ok = erlang:dist_ctrl_input_handler(DistHandle, InputHandler), + InputHandler ! DistHandle, + crypto:rand_seed_alg(crypto_cache), + reply(From, ok), + process_flag(priority, normal), + erlang:dist_ctrl_get_data_notification(DistHandle), + output_handler( + SendParams#params{dist_handle = DistHandle}, SendSeq); + %% + {?MODULE, From, {send, Data}} -> + case + encrypt_and_send_chunk( + SendParams, SendSeq, [?HANDSHAKE_CHUNK, Data]) + of + {SendParams_1, SendSeq_1, ok} -> + reply(From, ok), + handshake( + SendParams_1, SendSeq_1, RecvParams, RecvSeq, + Controller); + {_, _, Error} -> + reply(From, {error, closed}), + death_row({send, trace(Error)}) + end; + {?MODULE, From, recv} -> + case recv_and_decrypt_chunk(RecvParams, RecvSeq) of + {RecvParams_1, RecvSeq_1, {ok, _} = Reply} -> + reply(From, Reply), + handshake( + SendParams, SendSeq, RecvParams_1, RecvSeq_1, + Controller); + {_, _, Error} -> + reply(From, Error), + death_row({recv, trace(Error)}) + end; + {?MODULE, From, peername} -> + reply(From, inet:peername(Socket)), + handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller); + %% + _Alien -> + handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller) + end. + +recv_and_decrypt_chunk(#params{socket = Socket} = RecvParams, RecvSeq) -> + case gen_tcp:recv(Socket, 0) of + {ok, Chunk} -> + case decrypt_chunk(RecvParams, RecvSeq, Chunk) of + <<?HANDSHAKE_CHUNK, Cleartext/binary>> -> + {RecvParams, RecvSeq + 1, {ok, Cleartext}}; + OtherChunk when is_binary(OtherChunk) -> + {RecvParams, RecvSeq + 1, {error, decrypt_error}}; + #params{} = RecvParams_1 -> + recv_and_decrypt_chunk(RecvParams_1, 0); + error -> + {RecvParams, RecvSeq, {error, decrypt_error}} + end; + Error -> + {RecvParams, RecvSeq, Error} + end. + +%% ------------------------------------------------------------------------- +%% Output handler process +%% +%% The game here is to flush all dist_data and dist_tick messages, +%% prioritize dist_data over dist_tick, and to not use selective receive + +output_handler(Params, Seq) -> + receive + Msg -> + case Msg of + dist_data -> + output_handler_data(Params, Seq); + dist_tick -> + output_handler_tick(Params, Seq); + Other -> + %% Ignore + _ = trace(Other), + output_handler(Params, Seq) + end + end. + +output_handler_data(Params, Seq) -> + receive + Msg -> + case Msg of + dist_data -> + output_handler_data(Params, Seq); + dist_tick -> + output_handler_data(Params, Seq); + Other -> + %% Ignore + _ = trace(Other), + output_handler_data(Params, Seq) + end + after 0 -> + DistHandle = Params#params.dist_handle, + Q = get_data(DistHandle, empty_q()), + {Params_1, Seq_1} = output_handler_send(Params, Seq, Q), + erlang:dist_ctrl_get_data_notification(DistHandle), + output_handler(Params_1, Seq_1) + end. + +output_handler_tick(Params, Seq) -> + receive + Msg -> + case Msg of + dist_data -> + output_handler_data(Params, Seq); + dist_tick -> + output_handler_tick(Params, Seq); + Other -> + %% Ignore + _ = trace(Other), + output_handler_tick(Params, Seq) + end + after 0 -> + TickSize = 7 + rand:uniform(56), + TickData = binary:copy(<<0>>, TickSize), + case + encrypt_and_send_chunk(Params, Seq, [?TICK_CHUNK, TickData]) + of + {Params_1, Seq_1, ok} -> + output_handler(Params_1, Seq_1); + {_, _, Error} -> + _ = trace(Error), + death_row() + end + end. + +output_handler_send(Params, Seq, {_, Size, _} = Q) -> + if + ?CHUNK_SIZE < Size -> + output_handler_send(Params, Seq, Q, ?CHUNK_SIZE); + true -> + case get_data(Params#params.dist_handle, Q) of + {_, 0, _} -> + {Params, Seq}; + {_, Size, _} = Q_1 -> % Got no more + output_handler_send(Params, Seq, Q_1, Size); + Q_1 -> + output_handler_send(Params, Seq, Q_1) + end + end. + +output_handler_send(Params, Seq, Q, Size) -> + {Cleartext, Q_1} = deq_iovec(Size, Q), + case + encrypt_and_send_chunk(Params, Seq, [?DATA_CHUNK, Cleartext]) + of + {Params_1, Seq_1, ok} -> + output_handler_send(Params_1, Seq_1, Q_1); + {_, _, Error} -> + _ = trace(Error), + death_row() + end. + +%% ------------------------------------------------------------------------- +%% Input handler process +%% +%% Here is T = 0|infinity to steer if we should try to receive +%% more data or not; start with infinity, and when we get some +%% data try with 0 to see if more is waiting + +input_handler(#params{socket = Socket} = Params, Seq, Q, T) -> + receive + Msg -> + case Msg of + {tcp_passive, Socket} -> + ok = inet:setopts(Socket, [{active, ?TCP_ACTIVE}]), + Q_1 = + case T of + 0 -> + deliver_data(Params#params.dist_handle, Q); + infinity -> + Q + end, + input_handler(Params, Seq, Q_1, infinity); + {tcp, Socket, Chunk} -> + input_chunk(Params, Seq, Q, T, Chunk); + {tcp_closed, Socket} -> + exit(connection_closed); + Other -> + %% Ignore... + _ = trace(Other), + input_handler(Params, Seq, Q, T) + end + after T -> + Q_1 = deliver_data(Params#params.dist_handle, Q), + input_handler(Params, Seq, Q_1, infinity) + end. + +input_chunk(Params, Seq, Q, T, Chunk) -> + case decrypt_chunk(Params, Seq, Chunk) of + <<?DATA_CHUNK, Cleartext/binary>> -> + input_handler(Params, Seq + 1, enq_binary(Cleartext, Q), 0); + <<?TICK_CHUNK, _/binary>> -> + input_handler(Params, Seq + 1, Q, T); + OtherChunk when is_binary(OtherChunk) -> + _ = trace(invalid_chunk), + exit(connection_closed); + #params{} = Params_1 -> + input_handler(Params_1, 0, Q, T); + error -> + _ = trace(decrypt_error), + exit(connection_closed) + end. + +%% ------------------------------------------------------------------------- +%% erlang:dist_ctrl_* helpers + +%% Get data for sending from the VM and place it in a queue +%% +get_data(DistHandle, {Front, Size, Rear}) -> + get_data(DistHandle, Front, Size, Rear). +%% +get_data(DistHandle, Front, Size, Rear) -> + case erlang:dist_ctrl_get_data(DistHandle) of + none -> + {Front, Size, Rear}; + Bin when is_binary(Bin) -> + Len = byte_size(Bin), + get_data( + DistHandle, Front, Size + 4 + Len, + [Bin, <<Len:32>>|Rear]); + [Bin1, Bin2] -> + Len = byte_size(Bin1) + byte_size(Bin2), + get_data( + DistHandle, Front, Size + 4 + Len, + [Bin2, Bin1, <<Len:32>>|Rear]); + Iovec -> + Len = iolist_size(Iovec), + get_data( + DistHandle, Front, Size + 4 + Len, + lists:reverse(Iovec, [<<Len:32>>|Rear])) + end. + +%% De-packet and deliver received data to the VM from a queue +%% +deliver_data(DistHandle, Q) -> + case Q of + {[], Size, []} -> + Size = 0, % Assert + Q; + {[], Size, Rear} -> + [Bin|Front] = lists:reverse(Rear), + deliver_data(DistHandle, Front, Size, [], Bin); + {[Bin|Front], Size, Rear} -> + deliver_data(DistHandle, Front, Size, Rear, Bin) + end. +%% +deliver_data(DistHandle, Front, Size, Rear, Bin) -> + case Bin of + <<DataSizeA:32, DataA:DataSizeA/binary, + DataSizeB:32, DataB:DataSizeB/binary, Rest/binary>> -> + erlang:dist_ctrl_put_data(DistHandle, DataA), + erlang:dist_ctrl_put_data(DistHandle, DataB), + deliver_data( + DistHandle, + Front, Size - (4 + DataSizeA + 4 + DataSizeB), Rear, + Rest); + <<DataSize:32, Data:DataSize/binary, Rest/binary>> -> + erlang:dist_ctrl_put_data(DistHandle, Data), + deliver_data(DistHandle, Front, Size - (4 + DataSize), Rear, Rest); + <<DataSize:32, FirstData/binary>> -> + TotalSize = 4 + DataSize, + if + TotalSize =< Size -> + BinSize = byte_size(Bin), + {MoreData, Q} = + deq_iovec( + TotalSize - BinSize, + Front, Size - BinSize, Rear), + erlang:dist_ctrl_put_data(DistHandle, [FirstData|MoreData]), + deliver_data(DistHandle, Q); + true -> % Incomplete data + {[Bin|Front], Size, Rear} + end; + <<_/binary>> -> + BinSize = byte_size(Bin), + if + 4 =< Size -> % Fragmented header - extract a header bin + {RestHeader, {Front_1, _Size_1, Rear_1}} = + deq_iovec(4 - BinSize, Front, Size - BinSize, Rear), + Header = iolist_to_binary([Bin|RestHeader]), + deliver_data(DistHandle, Front_1, Size, Rear_1, Header); + true -> % Incomplete header + {[Bin|Front], Size, Rear} + end + end. + +%% ------------------------------------------------------------------------- +%% Encryption and decryption helpers + +encrypt_and_send_chunk( + #params{ + socket = Socket, + rekey_interval = Seq, + rekey_key = PubKeyB, + key = Key, + iv = {IVSalt, IVNo}, + hmac_algorithm = HmacAlgo} = Params, + Seq, Cleartext) -> + %% + KeyLen = byte_size(Key), + IVSaltLen = byte_size(IVSalt), + #key_pair{public = PubKeyA} = KeyPair = get_new_key_pair(), + case + gen_tcp:send( + Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, PubKeyA])) + of + ok -> + SharedSecret = compute_shared_secret(KeyPair, PubKeyB), + IV = <<(IVNo + Seq):48>>, + {Key_1, <<IVSalt_1:IVSaltLen/binary, IVNo_1:48>>} = + hmac_key_iv( + HmacAlgo, SharedSecret, [Key, IVSalt, IV], + KeyLen, IVSaltLen + 6), + Params_1 = Params#params{key = Key_1, iv = {IVSalt_1, IVNo_1}}, + Result = + gen_tcp:send(Socket, encrypt_chunk(Params_1, 0, Cleartext)), + {Params_1, 1, Result}; + SendError -> + {Params, Seq + 1, SendError} + end; +encrypt_and_send_chunk(#params{socket = Socket} = Params, Seq, Cleartext) -> + Result = gen_tcp:send(Socket, encrypt_chunk(Params, Seq, Cleartext)), + {Params, Seq + 1, Result}. + +encrypt_chunk( + #params{ + aead_cipher = AeadCipher, + iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen}, Seq, Cleartext) -> + %% + ChunkLen = iolist_size(Cleartext) + TagLen, + AAD = <<Seq:32, ChunkLen:32>>, + IVBin = <<IVSalt/binary, (IVNo + Seq):48>>, + {Ciphertext, CipherTag} = + crypto:block_encrypt(AeadCipher, Key, IVBin, {AAD, Cleartext, TagLen}), + Chunk = [Ciphertext,CipherTag], + Chunk. + +decrypt_chunk( + #params{ + aead_cipher = AeadCipher, + iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen} = Params, Seq, Chunk) -> + %% + ChunkLen = byte_size(Chunk), + if + ChunkLen < TagLen -> + error; + true -> + AAD = <<Seq:32, ChunkLen:32>>, + IVBin = <<IVSalt/binary, (IVNo + Seq):48>>, + CiphertextLen = ChunkLen - TagLen, + case Chunk of + <<Ciphertext:CiphertextLen/binary, + CipherTag:TagLen/binary>> -> + block_decrypt( + Params, Seq, AeadCipher, Key, IVBin, + {AAD, Ciphertext, CipherTag}); + _ -> + error + end + end. + +block_decrypt( + #params{ + rekey_key = #key_pair{public = PubKeyA} = KeyPair, + rekey_interval = RekeyInterval} = Params, + Seq, AeadCipher, Key, IV, Data) -> + %% + case crypto:block_decrypt(AeadCipher, Key, IV, Data) of + <<?REKEY_CHUNK, Rest/binary>> -> + PubKeyLen = byte_size(PubKeyA), + case Rest of + <<PubKeyB:PubKeyLen/binary>> -> + SharedSecret = compute_shared_secret(KeyPair, PubKeyB), + KeyLen = byte_size(Key), + IVLen = byte_size(IV), + IVSaltLen = IVLen - 6, + {Key_1, <<IVSalt:IVSaltLen/binary, IVNo:48>>} = + hmac_key_iv( + Params#params.hmac_algorithm, + SharedSecret, [Key, IV], KeyLen, IVLen), + Params#params{iv = {IVSalt, IVNo}, key = Key_1}; + _ -> + error + end; + Chunk when is_binary(Chunk) -> + case Seq of + RekeyInterval -> + %% This was one chunk too many without rekeying + error; + _ -> + Chunk + end; + error -> + error + end. + +%% ------------------------------------------------------------------------- +%% Queue of binaries i.e an iovec queue + +empty_q() -> + {[], 0, []}. + +enq_binary(Bin, {Front, Size, Rear}) -> + {Front, Size + byte_size(Bin), [Bin|Rear]}. + +deq_iovec(GetSize, {Front, Size, Rear}) when GetSize =< Size -> + deq_iovec(GetSize, Front, Size, Rear, []). +%% +deq_iovec(GetSize, Front, Size, Rear) -> + deq_iovec(GetSize, Front, Size, Rear, []). +%% +deq_iovec(GetSize, [], Size, Rear, Acc) -> + deq_iovec(GetSize, lists:reverse(Rear), Size, [], Acc); +deq_iovec(GetSize, [Bin|Front], Size, Rear, Acc) -> + BinSize = byte_size(Bin), + if + BinSize < GetSize -> + deq_iovec( + GetSize - BinSize, Front, Size - BinSize, Rear, [Bin|Acc]); + GetSize < BinSize -> + {Bin1,Bin2} = erlang:split_binary(Bin, GetSize), + {lists:reverse(Acc, [Bin1]), {[Bin2|Front], Size - GetSize, Rear}}; + true -> + {lists:reverse(Acc, [Bin]), {Front, Size - BinSize, Rear}} + end. + +%% ------------------------------------------------------------------------- + +death_row() -> death_row(connection_closed). +%% +death_row(normal) -> death_row(connection_closed); +death_row(Reason) -> receive after 5000 -> exit(Reason) end. + +%% ------------------------------------------------------------------------- + +%% Trace point +trace(Term) -> Term. + +%% Keep an eye on this Pid (debug) +-ifndef(undefined). +monitor_dist_proc(Pid) -> + Pid. +-else. +monitor_dist_proc(Pid) -> + spawn( + fun () -> + MRef = erlang:monitor(process, Pid), + receive + {'DOWN', MRef, _, _, normal} -> + error_logger:error_report( + [dist_proc_died, + {reason, normal}, + {pid, Pid}]); + {'DOWN', MRef, _, _, Reason} -> + error_logger:info_report( + [dist_proc_died, + {reason, Reason}, + {pid, Pid}]) + end + end), + Pid. +-endif. + +dbg() -> + dbg:stop(), + dbg:tracer(), + dbg:p(all, c), + dbg:tpl(?MODULE, trace, cx), + dbg:tpl(erlang, dist_ctrl_get_data_notification, cx), + dbg:tpl(erlang, dist_ctrl_get_data, cx), + dbg:tpl(erlang, dist_ctrl_put_data, cx), + ok. diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl index 7f3371da9a..76bf0fa895 100644 --- a/lib/ssl/test/make_certs.erl +++ b/lib/ssl/test/make_certs.erl @@ -377,7 +377,7 @@ req_cnf(Root, C) -> "default_bits = ", integer_to_list(C#config.default_bits), "\n" "RANDFILE = $ROOTDIR/RAND\n" "encrypt_key = no\n" - "default_md = md5\n" + "default_md = sha1\n" "#string_mask = pkix\n" "x509_extensions = ca_ext\n" "prompt = no\n" @@ -427,7 +427,7 @@ ca_cnf( ["crl_extensions = crl_ext\n" || C#config.v2_crls], "unique_subject = no\n" "default_days = 3600\n" - "default_md = md5\n" + "default_md = sha1\n" "preserve = no\n" "policy = policy_match\n" "\n" @@ -511,7 +511,7 @@ ca_cnf( ["crl_extensions = crl_ext\n" || C#config.v2_crls], "unique_subject = no\n" "default_days = 3600\n" - "default_md = md5\n" + "default_md = sha1\n" "preserve = no\n" "policy = policy_match\n" "\n" diff --git a/lib/ssl/test/openssl_alpn_SUITE.erl b/lib/ssl/test/openssl_alpn_SUITE.erl new file mode 100644 index 0000000000..5008dba922 --- /dev/null +++ b/lib/ssl/test/openssl_alpn_SUITE.erl @@ -0,0 +1,421 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(openssl_alpn_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_RENEGOTIATE, "R\n"). +-define(SLEEP, 1000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + %% Note: ALPN not supported in sslv3 + case ssl_test_lib:openssl_sane_dtls_alpn() of + true -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls_alpn() of + true -> + [ + {'tlsv1.3', [], alpn_tests()}, + {'tlsv1.2', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1.1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'dtlsv1.2', [], alpn_tests()}, + {'dtlsv1', [], alpn_tests()} + ]; + false -> + [ + {'tlsv1.3', [], alpn_tests()}, + {'tlsv1.2', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1.1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()} + ] + end. + +alpn_tests() -> + [erlang_client_alpn_openssl_server_alpn, + erlang_server_alpn_openssl_client_alpn, + erlang_client_alpn_openssl_server, + erlang_client_openssl_server_alpn, + erlang_server_alpn_openssl_client, + erlang_server_openssl_client_alpn]. + +alpn_npn_coexist() -> + [ + erlang_client_alpn_npn_openssl_server_alpn_npn, + erlang_server_alpn_npn_openssl_client_alpn_npn + ]. +rengotiation_tests() -> + case ssl_test_lib:sane_openssl_alpn_npn_renegotiate() of + true -> + [erlang_client_alpn_openssl_server_alpn_renegotiate, + erlang_server_alpn_openssl_client_alpn_renegotiate]; + false -> + [] + end. +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + case check_openssl_alpn_support(Config0) of + {skip, _} = Skip -> + Skip; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_openssl_server_alpn_renegotiate; + TestCase == erlang_server_alpn_openssl_client_alpn_renegotiate -> + {ok, Version} = application:get_env(ssl, protocol_version), + ssl_test_lib:check_sane_openssl_renegotaite(Config, Version); +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_npn_openssl_server_alpn_npn; + TestCase == erlang_server_alpn_npn_openssl_client_alpn_npn -> + ssl_test_lib:check_openssl_npn_support(Config); +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_client_alpn_openssl_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, + [{alpn_advertised_protocols, [<<"spdy/2">>]}], + [], + Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_client_openssl_server_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, + [], + ["-alpn", "spdy/2"], + Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_server_alpn_openssl_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, + [{alpn_preferred_protocols, [<<"spdy/2">>]}], + [], + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_server_openssl_client_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, + [], + ["-alpn", "spdy/2"], + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_client_alpn_openssl_server_alpn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_server_alpn_openssl_client_alpn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_server_alpn_npn_openssl_client_alpn_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + + +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------------------- +%%-------------------------------------------------------------------- +check_openssl_alpn_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "alpn") of + 0 -> + {skip, "Openssl not compiled with alpn support"}; + _ -> + Config + end. + +start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile], + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]} | ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-alpn", "http/1.0,spdy/2", "-msg", "-port", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-host", "localhost"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + +start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]}, + {client_preferred_next_protocols, {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", + "spdy/3", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]}, + {next_protocols_advertised, [<<"spdy/3">>, <<"http/1.1">>]} | ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", "spdy/3", + "-msg", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-host", "localhost"], + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + diff --git a/lib/ssl/test/openssl_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_cipher_suite_SUITE.erl new file mode 100644 index 0000000000..955eb914c0 --- /dev/null +++ b/lib/ssl/test/openssl_cipher_suite_SUITE.erl @@ -0,0 +1,801 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(openssl_cipher_suite_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, openssl_server}, + {group, openssl_client} + ]. + +all_protocol_groups() -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + %% TODO: Enable SRP, PSK suites (needs OpenSSL s_server conf) + %% TODO: Enable all "kex" on DTLS + [ + {openssl_server, all_protocol_groups()}, + {openssl_client, all_protocol_groups()}, + {'tlsv1.2', [], kex()}, + {'tlsv1.1', [], kex()}, + {'tlsv1', [], kex()}, + {'sslv3', [], kex()}, + {'dtlsv1.2', [], dtls_kex()}, + {'dtlsv1', [], dtls_kex()}, + {dhe_rsa, [],[dhe_rsa_3des_ede_cbc, + dhe_rsa_aes_128_cbc, + dhe_rsa_aes_256_cbc, + dhe_rsa_chacha20_poly1305 + ]}, + {ecdhe_rsa, [], [ecdhe_rsa_3des_ede_cbc, + ecdhe_rsa_aes_128_cbc, + ecdhe_rsa_aes_128_gcm, + ecdhe_rsa_aes_256_cbc, + ecdhe_rsa_aes_256_gcm, + ecdhe_rsa_chacha20_poly1305 + ]}, + {ecdhe_ecdsa, [],[ecdhe_ecdsa_rc4_128, + ecdhe_ecdsa_3des_ede_cbc, + ecdhe_ecdsa_aes_128_cbc, + ecdhe_ecdsa_aes_128_gcm, + ecdhe_ecdsa_aes_256_cbc, + ecdhe_ecdsa_aes_256_gcm, + ecdhe_ecdsa_chacha20_poly1305 + ]}, + {rsa, [], [rsa_3des_ede_cbc, + rsa_aes_128_cbc, + rsa_aes_256_cbc, + rsa_rc4_128 + ]}, + {dhe_dss, [], [dhe_dss_3des_ede_cbc, + dhe_dss_aes_128_cbc, + dhe_dss_aes_256_cbc]}, + %% {srp_rsa, [], [srp_rsa_3des_ede_cbc, + %% srp_rsa_aes_128_cbc, + %% srp_rsa_aes_256_cbc]}, + %% {srp_dss, [], [srp_dss_3des_ede_cbc, + %% srp_dss_aes_128_cbc, + %% srp_dss_aes_256_cbc]}, + %% {rsa_psk, [], [rsa_psk_3des_ede_cbc, + %% rsa_psk_rc4_128, + %% rsa_psk_aes_128_cbc, + %% rsa_psk_aes_256_cbc + %% ]}, + {dh_anon, [], [dh_anon_rc4_128, + dh_anon_3des_ede_cbc, + dh_anon_aes_128_cbc, + dh_anon_aes_128_gcm, + dh_anon_aes_256_cbc, + dh_anon_aes_256_gcm]}, + {ecdh_anon, [], [ecdh_anon_3des_ede_cbc, + ecdh_anon_aes_128_cbc, + ecdh_anon_aes_256_cbc + ]} + %% {srp_anon, [], [srp_anon_3des_ede_cbc, + %% srp_anon_aes_128_cbc, + %% srp_anon_aes_256_cbc]}, + %% {psk, [], [psk_3des_ede_cbc, + %% psk_rc4_128, + %% psk_aes_128_cbc, + %% psk_aes_128_ccm, + %% psk_aes_128_ccm_8, + %% psk_aes_256_cbc, + %% psk_aes_256_ccm, + %% psk_aes_256_ccm_8 + %% ]}, + %% {dhe_psk, [], [dhe_psk_3des_ede_cbc, + %% dhe_psk_rc4_128, + %% dhe_psk_aes_128_cbc, + %% dhe_psk_aes_128_ccm, + %% dhe_psk_aes_128_ccm_8, + %% dhe_psk_aes_256_cbc, + %% dhe_psk_aes_256_ccm, + %% dhe_psk_aes_256_ccm_8 + %% ]}, + %% {ecdhe_psk, [], [ecdhe_psk_3des_ede_cbc, + %% ecdhe_psk_rc4_128, + %% ecdhe_psk_aes_128_cbc, + %% ecdhe_psk_aes_128_ccm, + %% ecdhe_psk_aes_128_ccm_8, + %% ecdhe_psk_aes_256_cbc + %% ]} + ]. + +kex() -> + rsa() ++ ecdsa() ++ dss() ++ anonymous(). + +dtls_kex() -> %% Should be all kex in the future + dtls_rsa() ++ dss() ++ anonymous(). + +ssl3_kex() -> + ssl3_rsa() ++ ssl3_dss() ++ ssl3_anonymous(). + +rsa() -> + [{group, dhe_rsa}, + {group, ecdhe_rsa}, + {group, rsa} %%, {group, srp_rsa}, + %%{group, rsa_psk} + ]. + +dtls_rsa() -> + [ + {group, rsa} + %%,{group, rsa_psk} + ]. + +ssl3_rsa() -> + [{group, dhe_rsa}, + {group, rsa} + ]. + +ecdsa() -> + [{group, ecdhe_ecdsa}]. + +dss() -> + [{group, dhe_dss} + %%{group, srp_dss} + ]. + +ssl3_dss() -> + [{group, dhe_dss} + ]. + +anonymous() -> + [{group, dh_anon}, + {group, ecdh_anon} + %% {group, psk}, + %%{group, dhe_psk}, + %%{group, ecdhe_psk} + %%{group, srp_anon} + ]. + +ssl3_anonymous() -> + [{group, dh_anon}]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +%%-------------------------------------------------------------------- +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config), + do_init_per_group(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, {openssl_does_not_support, GroupName}} + end; + false -> + do_init_per_group(GroupName, Config) + end. +do_init_per_group(openssl_client, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, openssl}, {server_type, erlang} | Config]; +do_init_per_group(openssl_server, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, erlang}, {server_type, openssl} | Config]; +do_init_per_group(GroupName, Config) when GroupName == ecdh_anon; + GroupName == ecdhe_rsa; + GroupName == ecdhe_psk -> + case proplists:get_bool(ecdh, proplists:get_value(public_keys, crypto:supports())) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing EC crypto support"} + end; +do_init_per_group(ecdhe_ecdsa = GroupName, Config) -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(ecdh, PKAlg) andalso lists:member(ecdsa, PKAlg) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing EC crypto support"} + end; +do_init_per_group(dhe_dss = GroupName, Config) -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing DSS crypto support"} + end; +do_init_per_group(srp_dss = GroupName, Config) -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(dss, PKAlg) andalso lists:member(srp, PKAlg) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing DSS_SRP crypto support"} + end; +do_init_per_group(GroupName, Config) when GroupName == srp_anon; + GroupName == srp_rsa -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(srp, PKAlg) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing SRP crypto support"} + end; +do_init_per_group(dhe_psk = GroupName, Config) -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(dh, PKAlg) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing SRP crypto support"} + end; +do_init_per_group(GroupName, Config0) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, end_per_group(GroupName, Config0)); + false -> + init_certs(GroupName, Config0) + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) when TestCase == psk_3des_ede_cbc; + TestCase == srp_anon_3des_ede_cbc; + TestCase == dhe_psk_3des_ede_cbc; + TestCase == ecdhe_psk_3des_ede_cbc; + TestCase == srp_rsa_3des_ede_cbc; + TestCase == srp_dss_3des_ede_cbc; + TestCase == rsa_psk_3des_ede_cbc; + TestCase == rsa_3des_ede_cbc; + TestCase == dhe_rsa_3des_ede_cbc; + TestCase == dhe_dss_3des_ede_cbc; + TestCase == ecdhe_rsa_3des_ede_cbc; + TestCase == srp_anon_dss_3des_ede_cbc; + TestCase == dh_anon_3des_ede_cbc; + TestCase == ecdh_anon_3des_ede_cbc; + TestCase == ecdhe_ecdsa_3des_ede_cbc -> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(des_ede3, SupCiphers) of + true -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, "Missing 3DES crypto support"} + end; +init_per_testcase(TestCase, Config) when TestCase == psk_rc4_128; + TestCase == ecdhe_psk_rc4_128; + TestCase == dhe_psk_rc4_128; + TestCase == rsa_psk_rc4_128; + TestCase == rsa_rc4_128; + TestCase == ecdhe_rsa_rc4_128; + TestCase == ecdhe_ecdsa_rc4_128; + TestCase == dh_anon_rc4_128 -> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(rc4, SupCiphers) of + true -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, "Missing RC4 crypto support"} + end; +init_per_testcase(TestCase, Config) when TestCase == psk_aes_128_ccm_8; + TestCase == rsa_psk_aes_128_ccm_8; + TestCase == psk_aes_128_ccm_8; + TestCase == dhe_psk_aes_128_ccm_8; + TestCase == ecdhe_psk_aes_128_ccm_8 -> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(aes_128_ccm, SupCiphers) of + true -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, "Missing AES_128_CCM crypto support"} + end; +init_per_testcase(TestCase, Config) when TestCase == psk_aes_256_ccm_8; + TestCase == rsa_psk_aes_256_ccm_8; + TestCase == psk_aes_256_ccm_8; + TestCase == dhe_psk_aes_256_ccm_8; + TestCase == ecdhe_psk_aes_256_ccm_8 -> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(aes_256_ccm, SupCiphers) of + true -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, "Missing AES_256_CCM crypto support"} + end; +init_per_testcase(TestCase, Config) -> + Cipher = ssl_test_lib:test_cipher(TestCase, Config), + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(Cipher, SupCiphers) of + true -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, {Cipher, SupCiphers}} + end. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Initializtion ------------------------------------------ +%%-------------------------------------------------------------------- +init_certs(srp_rsa, Config) -> + {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()}, + {client_chain, ssl_test_lib:default_cert_chain_conf()}], + Config, ""), + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}} | ServerOpts], + client_config => [{srp_identity, {"Test-User", "secret"}} | ClientOpts]}} | + proplists:delete(tls_config, Config)]; +init_certs(srp_anon, Config) -> + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}], + client_config => [{srp_identity, {"Test-User", "secret"}}]}} | + proplists:delete(tls_config, Config)]; +init_certs(rsa_psk, Config) -> + Ext = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]), + {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain, + [[ssl_test_lib:digest()],[ssl_test_lib:digest()], + [ssl_test_lib:digest(), {extensions, Ext}]]}, + {client_chain, ssl_test_lib:default_cert_chain_conf()}], + Config, "_peer_keyEncipherment"), + PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}} | ServerOpts], + client_config => [{psk_identity, "Test-User"}, + {user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}} | ClientOpts]}} | + proplists:delete(tls_config, Config)]; +init_certs(rsa, Config) -> + Ext = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]), + {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain, + [[ssl_test_lib:digest()],[ssl_test_lib:digest()], + [ssl_test_lib:digest(), {extensions, Ext}]]} + ], + Config, "_peer_keyEncipherment"), + [{tls_config, #{server_config => ServerOpts, + client_config => ClientOpts}} | + proplists:delete(tls_config, Config)]; +init_certs(dhe_dss, Config) -> + {ClientOpts, ServerOpts} = ssl_test_lib:make_dsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()}, + {client_chain, ssl_test_lib:default_cert_chain_conf()}], + Config, ""), + [{tls_config, #{server_config => ServerOpts, + client_config => ClientOpts}} | + proplists:delete(tls_config, Config)]; +init_certs(srp_dss, Config) -> + {ClientOpts, ServerOpts} = ssl_test_lib:make_dsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()}, + {client_chain, ssl_test_lib:default_cert_chain_conf()}], + Config, ""), + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}} | ServerOpts], + client_config => [{srp_identity, {"Test-User", "secret"}} | ClientOpts]}} | + proplists:delete(tls_config, Config)]; +init_certs(GroupName, Config) when GroupName == dhe_rsa; + GroupName == ecdhe_rsa -> + {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()}, + {client_chain, ssl_test_lib:default_cert_chain_conf()}], + Config, ""), + [{tls_config, #{server_config => ServerOpts, + client_config => ClientOpts}} | + proplists:delete(tls_config, Config)]; +init_certs(GroupName, Config) when GroupName == dhe_ecdsa; + GroupName == ecdhe_ecdsa -> + {ClientOpts, ServerOpts} = ssl_test_lib:make_ecc_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()}, + {client_chain, ssl_test_lib:default_cert_chain_conf()}], + Config, ""), + [{tls_config, #{server_config => ServerOpts, + client_config => ClientOpts}} | + proplists:delete(tls_config, Config)]; +init_certs(GroupName, Config) when GroupName == psk; + GroupName == dhe_psk; + GroupName == ecdhe_psk -> + PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}}], + client_config => [{psk_identity, "Test-User"}, + {user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}}]}} | + proplists:delete(tls_config, Config)]; +init_certs(srp, Config) -> + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}], + client_config => [{srp_identity, {"Test-User", "secret"}}]}} | + proplists:delete(tls_config, Config)]; +init_certs(_GroupName, Config) -> + %% Anonymous does not need certs + [{tls_config, #{server_config => [], + client_config => []}} | + proplists:delete(tls_config, Config)]. +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% SRP -------------------------------------------------------- +%%-------------------------------------------------------------------- +srp_rsa_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_rsa, '3des_ede_cbc', Config). + +srp_rsa_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_rsa, 'aes_128_cbc', Config). + +srp_rsa_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_rsa, 'aes_256_cbc', Config). + +srp_dss_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_dss, '3des_ede_cbc', Config). + +srp_dss_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_dss, 'aes_128_cbc', Config). + +srp_dss_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_dss, 'aes_256_cbc', Config). + +%%-------------------------------------------------------------------- +%% PSK -------------------------------------------------------- +%%-------------------------------------------------------------------- +rsa_psk_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, '3des_ede_cbc', Config). + +rsa_psk_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_128_cbc', Config). + +rsa_psk_aes_128_ccm(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_128_ccm', Config). + +rsa_psk_aes_128_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_128_ccm_8', Config). + +rsa_psk_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_256_cbc', Config). + +rsa_psk_aes_256_ccm(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_256_ccm', Config). + +rsa_psk_aes_256_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_256_ccm_8', Config). + +rsa_psk_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'rc4_128', Config). + +%%-------------------------------------------------------------------- +%% RSA -------------------------------------------------------- +%%-------------------------------------------------------------------- +rsa_des_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'des_cbc', Config). + +rsa_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa, '3des_ede_cbc', Config). + +rsa_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'aes_128_cbc', Config). + +rsa_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'aes_256_cbc', Config). + +rsa_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'aes_128_gcm', Config). + +rsa_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'aes_256_gcm', Config). + +rsa_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'rc4_128', Config). +%%-------------------------------------------------------------------- +%% DHE_RSA -------------------------------------------------------- +%%-------------------------------------------------------------------- +dhe_rsa_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, '3des_ede_cbc', Config). + +dhe_rsa_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, 'aes_128_cbc', Config). + +dhe_rsa_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, 'aes_128_gcm', Config). + +dhe_rsa_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, 'aes_256_cbc', Config). + +dhe_rsa_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, 'aes_256_gcm', Config). + +dhe_rsa_chacha20_poly1305(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, 'chacha20_poly1305', Config). +%%-------------------------------------------------------------------- +%% ECDHE_RSA -------------------------------------------------------- +%%-------------------------------------------------------------------- +ecdhe_rsa_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, '3des_ede_cbc', Config). + +ecdhe_rsa_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'aes_128_cbc', Config). + +ecdhe_rsa_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'aes_128_gcm', Config). + +ecdhe_rsa_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'aes_256_cbc', Config). + +ecdhe_rsa_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'aes_256_gcm', Config). + +ecdhe_rsa_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'rc4_128', Config). + +ecdhe_rsa_chacha20_poly1305(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'chacha20_poly1305', Config). + +%%-------------------------------------------------------------------- +%% ECDHE_ECDSA -------------------------------------------------------- +%%-------------------------------------------------------------------- +ecdhe_ecdsa_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'rc4_128', Config). + +ecdhe_ecdsa_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, '3des_ede_cbc', Config). + +ecdhe_ecdsa_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_128_cbc', Config). + +ecdhe_ecdsa_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_128_gcm', Config). + +ecdhe_ecdsa_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_256_cbc', Config). + +ecdhe_ecdsa_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_256_gcm', Config). + +ecdhe_ecdsa_chacha20_poly1305(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'chacha20_poly1305', Config). +%%-------------------------------------------------------------------- +%% DHE_DSS -------------------------------------------------------- +%%-------------------------------------------------------------------- +dhe_dss_des_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, 'des_cbc', Config). + +dhe_dss_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, '3des_ede_cbc', Config). + +dhe_dss_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, 'aes_128_cbc', Config). + +dhe_dss_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, 'aes_256_cbc', Config). + +dhe_dss_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, 'aes_128_gcm', Config). + +dhe_dss_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, 'aes_256_gcm', Config). + +%%-------------------------------------------------------------------- +%% Anonymous -------------------------------------------------------- +%%-------------------------------------------------------------------- +dh_anon_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, '3des_ede_cbc', Config). + +dh_anon_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, 'aes_128_cbc', Config). + +dh_anon_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, 'aes_128_gcm', Config). + +dh_anon_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, 'aes_256_cbc', Config). + +dh_anon_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, 'aes_256_gcm', Config). + +dh_anon_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, 'rc4_128', Config). + +ecdh_anon_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdh_anon, '3des_ede_cbc', Config). + +ecdh_anon_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdh_anon, 'aes_128_cbc', Config). + +ecdh_anon_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdh_anon, 'aes_256_cbc', Config). + +srp_anon_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_anon, '3des_ede_cbc', Config). + +srp_anon_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_anon, 'aes_128_cbc', Config). + +srp_anon_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_anon, 'aes_256_cbc', Config). + +dhe_psk_des_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'des_cbc', Config). + +dhe_psk_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'rc4_128', Config). + +dhe_psk_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, '3des_ede_cbc', Config). + +dhe_psk_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_128_cbc', Config). + +dhe_psk_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_256_cbc', Config). + +dhe_psk_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_128_gcm', Config). + +dhe_psk_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_256_gcm', Config). + +dhe_psk_aes_128_ccm(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_128_ccm', Config). + +dhe_psk_aes_256_ccm(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_256_ccm', Config). + +dhe_psk_aes_128_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_128_ccm_8', Config). + +dhe_psk_aes_256_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_256_ccm_8', Config). + +ecdhe_psk_des_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'des_cbc', Config). + +ecdhe_psk_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'rc4_128', Config). + +ecdhe_psk_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, '3des_ede_cbc', Config). + +ecdhe_psk_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_128_cbc', Config). + +ecdhe_psk_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_256_cbc', Config). + +ecdhe_psk_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_128_gcm', Config). + +ecdhe_psk_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_256_gcm', Config). + +ecdhe_psk_aes_128_ccm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_128_ccm', Config). + +ecdhe_psk_aes_128_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_128_ccm_8', Config). + +psk_des_cbc(Config) when is_list(Config) -> + run_ciphers_test(psk, 'des_cbc', Config). + +psk_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(psk, 'rc4_128', Config). + +psk_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(psk, '3des_ede_cbc', Config). + +psk_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_128_cbc', Config). + +psk_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_256_cbc', Config). + +psk_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_128_gcm', Config). + +psk_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_256_gcm', Config). + +psk_aes_128_ccm(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_128_ccm', Config). + +psk_aes_256_ccm(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_256_ccm', Config). + +psk_aes_128_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_128_ccm_8', Config). + +psk_aes_256_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_256_ccm_8', Config). + +%%-------------------------------------------------------------------- +%% Internal functions ---------------------------------------------- +%%-------------------------------------------------------------------- +run_ciphers_test(Kex, Cipher, Config) -> + Version = ssl_test_lib:protocol_version(Config), + TestCiphers = test_ciphers(Kex, Cipher, Version), + + case TestCiphers of + [_|_] -> + lists:foreach(fun(TestCipher) -> + cipher_suite_test(TestCipher, Version, Config) + end, TestCiphers); + [] -> + {skip, {not_sup, Kex, Cipher, Version}} + end. + +cipher_suite_test(CipherSuite, _Version, Config) -> + #{server_config := SOpts, + client_config := COpts} = proplists:get_value(tls_config, Config), + ServerOpts = ssl_test_lib:ssl_options(SOpts, Config), + ClientOpts = ssl_test_lib:ssl_options(COpts, Config), + ct:log("Testing CipherSuite ~p~n", [CipherSuite]), + ct:log("Server Opts ~p~n", [ServerOpts]), + ct:log("Client Opts ~p~n", [ClientOpts]), + ssl_test_lib:basic_test([{ciphers, [CipherSuite]} | COpts], SOpts, Config). + + +test_ciphers(Kex, Cipher, Version) -> + Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, Version) ++ ssl:cipher_suites(anonymous, Version), + [{key_exchange, + fun(Kex0) when Kex0 == Kex -> true; + (_) -> false + end}, + {cipher, + fun(Cipher0) when Cipher0 == Cipher -> true; + (_) -> false + end}]), + ct:log("Version ~p Testing ~p~n", [Version, Ciphers]), + OpenSSLCiphers = openssl_ciphers(), + ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]), + lists:filter(fun(C) -> + ct:log("Cipher ~p~n", [C]), + lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers) + end, Ciphers). + + +openssl_ciphers() -> + Str = os:cmd("openssl ciphers"), + string:split(string:strip(Str, right, $\n), ":", all). diff --git a/lib/ssl/test/openssl_client_cert_SUITE.erl b/lib/ssl/test/openssl_client_cert_SUITE.erl new file mode 100644 index 0000000000..b327988744 --- /dev/null +++ b/lib/ssl/test/openssl_client_cert_SUITE.erl @@ -0,0 +1,350 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(openssl_client_cert_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, openssl_client} + ]. + +groups() -> + [ + {openssl_client, [], protocol_groups()}, + {'tlsv1.3', [], tls_1_3_protocol_groups()}, + {'tlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1.1', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1', [], pre_tls_1_3_protocol_groups()}, + {'sslv3', [], ssl_protocol_groups()}, + {'dtlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'dtlsv1', [], pre_tls_1_3_protocol_groups()}, + {rsa, [], all_version_tests()}, + {ecdsa, [], all_version_tests()}, + {dsa, [], all_version_tests()}, + {rsa_1_3, [], all_version_tests() ++ tls_1_3_tests() ++ [unsupported_sign_algo_client_auth, + unsupported_sign_algo_cert_client_auth]}, + {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} + ]. + +protocol_groups() -> + [{group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +ssl_protocol_groups() -> + [{group, rsa}, + {group, dsa}]. + +pre_tls_1_3_protocol_groups() -> + [{group, rsa}, + {group, ecdsa}, + {group, dsa}]. + +tls_1_3_protocol_groups() -> + [{group, rsa_1_3}, + {group, ecdsa_1_3}]. + +tls_1_3_tests() -> + [ + hello_retry_request, + custom_groups, + hello_retry_client_auth, + hello_retry_client_auth_empty_cert_accepted, + hello_retry_client_auth_empty_cert_rejected + ]. + +all_version_tests() -> + [ + no_auth, + auth, + client_auth_empty_cert_accepted, + client_auth_empty_cert_rejected, + client_auth_partial_chain, + client_auth_allow_partial_chain, + client_auth_do_not_allow_partial_chain, + client_auth_partial_chain_fun_fail, + missing_root_cert_no_auth + %%invalid_signature_client + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(openssl_client, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, openssl}, {server_type, erlang} | Config]; +init_per_group(Group, Config0) when Group == rsa; + Group == rsa_1_3 -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + %% Make sure _rsa* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) -> + true; + (ecdhe_rsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; +init_per_group(Group, Config0) when Group == ecdsa; + Group == ecdsa_1_3 -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse + lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + %% Make sure ecdh* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) -> + true; + (ecdhe_ecdsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(Group, Config0) when Group == dsa -> + PKAlg = crypto:supports(public_keys), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + Config = ssl_test_lib:make_dsa_cert(Config0), + COpts = proplists:get_value(client_dsa_opts, Config), + SOpts = proplists:get_value(server_dsa_opts, Config), + %% Make sure dhe_dss* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) -> + true; + (dhe_dss) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, dsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing DSS crypto support"} + end; +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + [{version, GroupName} + | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing openssl support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. +init_per_testcase(TestCase, Config) when + TestCase == client_auth_empty_cert_accepted; + TestCase == client_auth_empty_cert_rejected -> + Version = proplists:get_value(version,Config), + case Version of + sslv3 -> + %% Openssl client sends "No Certificate Reserved" warning ALERT + %% instead of sending EMPTY cert message in SSL-3.0 so empty cert test are not + %% relevant + {skip, openssl_behaves_differently}; + _ -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config + end; +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +no_auth() -> + ssl_cert_tests:no_auth(). + +no_auth(Config) -> + ssl_cert_tests:no_auth(Config). +%%-------------------------------------------------------------------- +auth() -> + ssl_cert_tests:auth(). +auth(Config) -> + ssl_cert_tests:auth(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + ssl_cert_tests:client_auth_empty_cert_accepted(). +client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + ssl_cert_tests:client_auth_empty_cert_rejected(). +client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:client_auth_empty_cert_rejected(Config). +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + ssl_cert_tests:client_auth_partial_chain(). +client_auth_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + ssl_cert_tests:client_auth_allow_partial_chain(). +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_allow_partial_chain(Config). +%%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(). +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(). +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + ssl_cert_tests:missing_root_cert_no_auth(). +missing_root_cert_no_auth(Config) when is_list(Config) -> + ssl_cert_tests:missing_root_cert_no_auth(Config). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_client(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). +%%-------------------------------------------------------------------- +invalid_signature_server() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_server(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test Cases ------------------------------------------------ +%%-------------------------------------------------------------------- +hello_retry_request() -> + ssl_cert_tests:hello_retry_request(). +hello_retry_request(Config) -> + ssl_cert_tests:hello_retry_request(Config). +%%-------------------------------------------------------------------- +custom_groups() -> + ssl_cert_tests:custom_groups(). +custom_groups(Config) -> + ssl_cert_tests:custom_groups(Config). +unsupported_sign_algo_cert_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(). +unsupported_sign_algo_cert_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(Config). +unsupported_sign_algo_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_client_auth(). +unsupported_sign_algo_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + ssl_cert_tests:hello_retry_client_auth(). +hello_retry_client_auth(Config) -> + ssl_cert_tests:hello_retry_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(). +hello_retry_client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(). +hello_retry_client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(Config). diff --git a/lib/ssl/test/openssl_npn_SUITE.erl b/lib/ssl/test/openssl_npn_SUITE.erl new file mode 100644 index 0000000000..0294f4997f --- /dev/null +++ b/lib/ssl/test/openssl_npn_SUITE.erl @@ -0,0 +1,311 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(openssl_npn_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_RENEGOTIATE, "R\n"). +-define(SLEEP, 1000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + %% NPN is not supported in TLS-1.3 (replaced by ALPN and deprecated in TLS 1.2) + %% OpenSSL DTLS support for NPN is either not there or broken. + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}]. + +groups() -> + [{'tlsv1.2', [], npn_tests() ++ npn_renegotiate_tests()}, + {'tlsv1.1', [], npn_tests() ++ npn_renegotiate_tests()}, + {'tlsv1', [], npn_tests() ++ npn_renegotiate_tests()} + ]. + +npn_tests() -> + [erlang_client_openssl_server_npn, + erlang_server_openssl_client_npn, + erlang_server_openssl_client_npn_only_client, + erlang_server_openssl_client_npn_only_server, + erlang_client_openssl_server_npn_only_client, + erlang_client_openssl_server_npn_only_server]. + +npn_renegotiate_tests() -> + case ssl_test_lib:sane_openssl_alpn_npn_renegotiate() of + true -> + [erlang_server_openssl_client_npn_renegotiate, + erlang_client_openssl_server_npn_renegotiate]; + false -> + [] + end. + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + case check_openssl_npn_support(Config0) of + {skip, _} = Skip -> + Skip; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(TestCase, Config) + when TestCase == erlang_client_npn_openssl_server_npn_renegotiate; + TestCase == erlang_server_npn_openssl_client_npn_renegotiate -> + {ok, Version} = application:get_env(ssl, protocol_version), + ssl_test_lib:check_sane_openssl_renegotaite(Config, Version); +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +erlang_client_openssl_server_npn() -> + [{doc,"Test erlang client with openssl server doing npn negotiation"}]. + +erlang_client_openssl_server_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, + fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- +erlang_client_openssl_server_npn_renegotiate() -> + [{doc,"Test erlang client with openssl server doing npn negotiation and renegotiate"}]. + +erlang_client_openssl_server_npn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, + fun(Client, OpensslPort) -> + true = port_command(OpensslPort, + ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). +%%-------------------------------------------------------------------------- +erlang_server_openssl_client_npn() -> + [{doc,"Test erlang server with openssl client and npn negotiation"}]. + +erlang_server_openssl_client_npn(Config) when is_list(Config) -> + + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- +erlang_server_openssl_client_npn_renegotiate() -> + [{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}]. + +erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, + ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). +%%-------------------------------------------------------------------------- +erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, [], + ["-nextprotoneg", "spdy/2"], Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, + [{client_preferred_next_protocols, + {client, [<<"spdy/2">>], <<"http/1.1">>}}], [], + Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- +erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, + [{next_protocols_advertised, [<<"spdy/2">>]}], [], + Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, [], ["-nextprotoneg", "spdy/2"], + Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------------------- +%%-------------------------------------------------------------------- + +start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile], + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ClientOpts}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]} | ServerOpts0], + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-nextprotoneg", "http/1.0,spdy/2", "-msg", "-connect", + ssl_test_lib:hostname_format(Hostname) ++ ":" + ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + +check_openssl_npn_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "nextprotoneg") of + 0 -> + {skip, "Openssl not compiled with nextprotoneg support"}; + _ -> + Config + end. diff --git a/lib/ssl/test/openssl_reject_SUITE.erl b/lib/ssl/test/openssl_reject_SUITE.erl new file mode 100644 index 0000000000..deefd11823 --- /dev/null +++ b/lib/ssl/test/openssl_reject_SUITE.erl @@ -0,0 +1,209 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(openssl_reject_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 1000). +-define(OPENSSL_GARBAGE, "P\n"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}]. + +groups() -> + [{'tlsv1.2', [], all_versions_tests()}, + {'tlsv1.1', [], all_versions_tests()}, + {'tlsv1', [], all_versions_tests()}, + {'sslv3', [], all_versions_tests()} + ]. + +all_versions_tests() -> + [ + erlang_client_bad_openssl_server, + ssl2_erlang_server_openssl_client + ]. + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(ssl2_erlang_server_openssl_client, Config) -> + case ssl_test_lib:supports_ssl_tls_version(sslv2) of + true -> + Config; + false -> + {skip, "sslv2 not supported by openssl"} + end; +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +erlang_client_bad_openssl_server() -> + [{doc,"Test what happens if openssl server sends garbage to erlang ssl client"}]. +erlang_client_bad_openssl_server(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, server_sent_garbage, []}}, + {options, + [{versions, [Version]} | ClientOpts]}]), + + %% Send garbage + true = port_command(OpensslPort, ?OPENSSL_GARBAGE), + + ct:sleep(?SLEEP), + + Client0 ! server_sent_garbage, + + ssl_test_lib:check_result(Client0, true), + + ssl_test_lib:close(Client0), + + %% Make sure openssl does not hang and leave zombie process + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, + [{versions, [Version]} | ClientOpts]}]), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client1), + process_flag(trap_exit, false). + +%%-------------------------------------------------------------------- +ssl2_erlang_server_openssl_client() -> + [{doc,"Test that ssl v2 clients are rejected"}]. + +ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Exe = "openssl", + Args = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), + "-ssl2", "-msg"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), + ssl_test_lib:consume_port_exit(OpenSslPort), + ssl_test_lib:check_server_alert(Server, unexpected_message), + process_flag(trap_exit, false). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +server_sent_garbage(Socket) -> + receive + server_sent_garbage -> + {error, closed} == ssl:send(Socket, "data") + + end. diff --git a/lib/ssl/test/openssl_renegotiate_SUITE.erl b/lib/ssl/test/openssl_renegotiate_SUITE.erl new file mode 100644 index 0000000000..91a8175ac6 --- /dev/null +++ b/lib/ssl/test/openssl_renegotiate_SUITE.erl @@ -0,0 +1,341 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(openssl_renegotiate_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 1000). +-define(OPENSSL_RENEGOTIATE, "R\n"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{'tlsv1.2', [], all_versions_tests()}, + {'tlsv1.1', [], all_versions_tests()}, + {'tlsv1', [], all_versions_tests()}, + {'sslv3', [], all_versions_tests()}, + {'dtlsv1.2', [], all_versions_tests()}, + {'dtlsv1', [], all_versions_tests()} + ]; + false -> + [{'tlsv1.2', [], all_versions_tests()}, + {'tlsv1.1', [], all_versions_tests()}, + {'tlsv1', [], all_versions_tests()}, + {'sslv3', [], all_versions_tests()} + ] + end. + +all_versions_tests() -> + [ + erlang_client_openssl_server_renegotiate, + erlang_client_openssl_server_renegotiate_after_client_data, + erlang_client_openssl_server_nowrap_seqnum, + erlang_server_openssl_client_nowrap_seqnum + ]. + + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(TestCase, Config) + when TestCase == erlang_client_openssl_server_renegotiate; + TestCase == erlang_client_openssl_server_nowrap_seqnum; + TestCase == erlang_server_openssl_client_nowrap_seqnum; + TestCase == erlang_client_openssl_server_renegotiate_after_client_data + -> + {ok, Version} = application:get_env(ssl, protocol_version), + ssl_test_lib:check_sane_openssl_renegotaite(Config, Version); +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +erlang_client_openssl_server_renegotiate() -> + [{doc,"Test erlang client when openssl server issuses a renegotiate"}]. +erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl", + OpenSslData = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile, "-msg"], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + delayed_send, [[ErlData, OpenSslData]]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, OpenSslData), + + ssl_test_lib:check_result(Client, OpenSslData), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- +erlang_client_openssl_server_renegotiate_after_client_data() -> + [{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}]. +erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl", + OpenSslData = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile, "-msg"], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + send_wait_send, [[ErlData, OpenSslData]]}}, + {options, [{reuse_sessions, false} |ClientOpts]}]), + + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, OpenSslData), + + ssl_test_lib:check_result(Client, OpenSslData), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. + +%%-------------------------------------------------------------------- +erlang_client_openssl_server_nowrap_seqnum() -> + [{doc, "Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. +erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl\n", + N = 10, + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile, "-msg"], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[ErlData, N+2]]}}, + {options, [{reuse_sessions, false}, + {renegotiate_at, N} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client), + process_flag(trap_exit, false). +%%-------------------------------------------------------------------- +erlang_server_openssl_client_nowrap_seqnum() -> + [{doc, "Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. +erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + N = 10, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[Data, N+2]]}}, + {options, [{renegotiate_at, N}, {reuse_sessions, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client","-connect", ssl_test_lib:hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-msg"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + true = port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, ok), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close(Server), + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +delayed_send(Socket, [ErlData, OpenSslData]) -> + ct:sleep(?SLEEP), + ssl:send(Socket, ErlData), + ssl_test_lib:active_recv(Socket, length(OpenSslData)). + + +send_wait_send(Socket, [ErlData, OpenSslData]) -> + ssl:send(Socket, ErlData), + ct:sleep(?SLEEP), + ssl:send(Socket, ErlData), + ssl_test_lib:active_recv(Socket, length(OpenSslData)). + diff --git a/lib/ssl/test/openssl_server_cert_SUITE.erl b/lib/ssl/test/openssl_server_cert_SUITE.erl new file mode 100644 index 0000000000..c2af864a92 --- /dev/null +++ b/lib/ssl/test/openssl_server_cert_SUITE.erl @@ -0,0 +1,373 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(openssl_server_cert_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, openssl_server}]. + +groups() -> + [ + {openssl_server, [], protocol_groups()}, + {'tlsv1.3', [], tls_1_3_protocol_groups()}, + {'tlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1.1', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1', [], pre_tls_1_3_protocol_groups()}, + {'sslv3', [], ssl_protocol_groups()}, + {'dtlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'dtlsv1', [], pre_tls_1_3_protocol_groups()}, + {rsa, [], all_version_tests()}, + {ecdsa, [], all_version_tests()}, + {dsa, [], all_version_tests()}, + {rsa_1_3, [], all_version_tests() ++ tls_1_3_tests()}, + %% TODO: Create proper conf of openssl server + %%++ [unsupported_sign_algo_client_auth, + %% unsupported_sign_algo_cert_client_auth]}, + {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} + ]. + +protocol_groups() -> + [{group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +ssl_protocol_groups() -> + [{group, rsa}, + {group, dsa}]. + +pre_tls_1_3_protocol_groups() -> + [{group, rsa}, + {group, ecdsa}, + {group, dsa}]. + +tls_1_3_protocol_groups() -> + [{group, rsa_1_3}, + {group, ecdsa_1_3}]. + +tls_1_3_tests() -> + [ + hello_retry_request, + custom_groups, + hello_retry_client_auth, + hello_retry_client_auth_empty_cert_accepted, + hello_retry_client_auth_empty_cert_rejected + ]. + +all_version_tests() -> + [ + no_auth, + auth, + missing_root_cert_no_auth + %%invalid_signature_client + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(openssl_server, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, erlang}, {server_type, openssl} | Config]; +init_per_group(rsa = Group, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + %% Make sure _rsa* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) -> + true; + (ecdhe_rsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; +init_per_group(rsa_1_3 = Group, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + %% Make sure _rsa* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(undefined, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; +init_per_group(ecdsa = Group, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse + lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + %% Make sure ecdh* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) -> + true; + (ecdhe_ecdsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(ecdsa_1_3 = Group, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse + lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + %% Make sure ecdh* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(undefined, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(Group, Config0) when Group == dsa -> + PKAlg = crypto:supports(public_keys), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + Config = ssl_test_lib:make_dsa_cert(Config0), + COpts = proplists:get_value(client_dsa_opts, Config), + SOpts = proplists:get_value(server_dsa_opts, Config), + %% Make sure dhe_dss* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) -> + true; + (dhe_dss) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, dsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing DSS crypto support"} + end; +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + [{version, GroupName} + | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing openssl support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +no_auth() -> + ssl_cert_tests:no_auth(). + +no_auth(Config) -> + ssl_cert_tests:no_auth(Config). +%%-------------------------------------------------------------------- +auth() -> + ssl_cert_tests:auth(). +auth(Config) -> + ssl_cert_tests:auth(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + ssl_cert_tests:client_auth_empty_cert_accepted(). +client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + ssl_cert_tests:client_auth_empty_cert_rejected(). +client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:client_auth_empty_cert_rejected(Config). +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + ssl_cert_tests:client_auth_partial_chain(). +client_auth_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + ssl_cert_tests:client_auth_allow_partial_chain(). +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_allow_partial_chain(Config). +%%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(). +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(). +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + ssl_cert_tests:missing_root_cert_no_auth(). +missing_root_cert_no_auth(Config) when is_list(Config) -> + ssl_cert_tests:missing_root_cert_no_auth(Config). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_client(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). +%%-------------------------------------------------------------------- +invalid_signature_server() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_server(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test Cases ------------------------------------------------ +%%-------------------------------------------------------------------- +hello_retry_request() -> + ssl_cert_tests:hello_retry_request(). +hello_retry_request(Config) -> + ssl_cert_tests:hello_retry_request(Config). +%%-------------------------------------------------------------------- +custom_groups() -> + ssl_cert_tests:custom_groups(). +custom_groups(Config) -> + ssl_cert_tests:custom_groups(Config). +unsupported_sign_algo_cert_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(). +unsupported_sign_algo_cert_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(Config). +unsupported_sign_algo_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_client_auth(). +unsupported_sign_algo_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + ssl_cert_tests:hello_retry_client_auth(). +hello_retry_client_auth(Config) -> + ssl_cert_tests:hello_retry_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(). +hello_retry_client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(). +hello_retry_client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(Config). diff --git a/lib/ssl/test/openssl_session_SUITE.erl b/lib/ssl/test/openssl_session_SUITE.erl new file mode 100644 index 0000000000..24dcaa7817 --- /dev/null +++ b/lib/ssl/test/openssl_session_SUITE.erl @@ -0,0 +1,262 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(openssl_session_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 1000). +-define(EXPIRE, 10). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{'tlsv1.2', [], tests()}, + {'tlsv1.1', [], tests()}, + {'tlsv1', [], tests()}, + {'sslv3', [], tests()}, + {'dtlsv1.2', [], dtls_tests()}, + {'dtlsv1', [], dtls_tests()} + ]; + false -> + [{'tlsv1.2', [], tests()}, + {'tlsv1.1', [], tests()}, + {'tlsv1', [], tests()}, + {'sslv3', [], tests()} + ] + end. + +tests() -> + [ + reuse_session_erlang_server, + reuse_session_erlang_client + ]. + +dtls_tests() -> + [ + reuse_session_erlang_server + ]. + + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(reuse_session_erlang_client, Config) -> + ct:timetrap(?EXPIRE * 1000 * 5), + ssl:stop(), + application:load(ssl), + application:set_env(ssl, session_lifetime, ?EXPIRE), + ssl:start(), + Config; + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(reuse_session_erlang_client, Config) -> + application:unset_env(ssl, session_lifetime), + Config; +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +reuse_session_erlang_server() -> + [{doc, "Test erlang server with openssl client that reconnects with the" + "same session id, to test reusing of sessions."}]. +reuse_session_erlang_server(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, active_recv, [length(Data)]}}, + {reconnect_times, 5}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname) + ++ ":" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-reconnect"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + true = port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, Data), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close(Server), + ssl_test_lib:close_port(OpenSslPort). + +%%-------------------------------------------------------------------- + +reuse_session_erlang_client() -> + [{doc, "Test erlang ssl client that wants to reuse sessions"}]. +reuse_session_erlang_client(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + CACertFile = proplists:get_value(cacertfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + "-cert", CertFile,"-key", KeyFile, "-CAfile", CACertFile], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, tls), + + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save}, {verify, verify_peer}| ClientOpts]}]), + + SID = receive + {Client0, Id0} -> + Id0 + end, + + ssl_test_lib:close(Client0), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_session, SID} | ClientOpts]}]), + receive + {Client1, SID} -> + ok + after ?SLEEP -> + ct:fail(session_not_reused) + end, + + + ssl_test_lib:close(Client1), + %% Make sure session is unregistered due to expiration + ct:sleep(20000), + + Client2 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client2, ID} -> + case ID of + SID -> + ct:fail(expired_session_reused); + _ -> + ok + end + end, + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client2). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- diff --git a/lib/ssl/test/openssl_sni_SUITE.erl b/lib/ssl/test/openssl_sni_SUITE.erl new file mode 100644 index 0000000000..26f08e36c0 --- /dev/null +++ b/lib/ssl/test/openssl_sni_SUITE.erl @@ -0,0 +1,251 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(openssl_sni_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_RENEGOTIATE, "R\n"). +-define(SLEEP, 1000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + %% Note: SNI not supported in sslv3 + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'} + %% Seems broken in openssl + %%{group, 'dtlsv1.2'}, + %%{group, 'dtlsv1'} + ]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{'tlsv1.2', [], sni_tests()}, + {'tlsv1.1', [], sni_tests()}, + {'tlsv1', [], sni_tests()} + %% Seems broken in openssl + %%{'dtlsv1.2', [], sni_tests()}, + %%{'dtlsv1', [], sni_tests()} + ]; + false -> + [{'tlsv1.2', [], sni_tests()}, + {'tlsv1.1', [], sni_tests()}, + {'tlsv1', [], sni_tests()} + ] + end. + +sni_tests() -> + [erlang_server_openssl_client_sni_match, + erlang_server_openssl_client_sni_match_fun, + erlang_server_openssl_client_sni_no_match, + erlang_server_openssl_client_sni_no_match_fun, + erlang_server_openssl_client_sni_no_header, + erlang_server_openssl_client_sni_no_header_fun]. + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + case check_openssl_sni_support(Config0) of + {skip, _} = Skip -> + Skip; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config = ssl_test_lib:make_rsa_cert(Config0), + RsaOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + [{sni_server_opts, [{sni_hosts, + [{"a.server", [ + {certfile, proplists:get_value(certfile, RsaOpts)}, + {keyfile, proplists:get_value(keyfile, RsaOpts)} + ]}, + {"b.server", [ + {certfile, proplists:get_value(certfile, RsaOpts)}, + {keyfile, proplists:get_value(keyfile, RsaOpts)} + ]} + ]}]} | Config] + catch _:_ -> + {skip, "Crypto did not start"} + end + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 10}), + Config. + + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +erlang_server_openssl_client_sni_no_header(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, undefined, undefined, "server Peer cert"). + +erlang_server_openssl_client_sni_no_header_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, undefined, undefined, "server Peer cert"). + +erlang_server_openssl_client_sni_match(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, "a.server", "a.server", "server Peer cert"). + +erlang_server_openssl_client_sni_match_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, "a.server", "a.server", "server Peer cert"). + +erlang_server_openssl_client_sni_no_match(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, "c.server", undefined, "server Peer cert"). + +erlang_server_openssl_client_sni_no_match_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, "c.server", undefined, "server Peer cert"). + + +erlang_server_openssl_client_sni_test(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + Version = ssl_test_lib:protocol_version(Config), + ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", + [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + ServerOptions = proplists:get_value(sni_server_opts, Config) ++ proplists:get_value(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Exe = "openssl", + ClientArgs = case SNIHostname of + undefined -> + openssl_client_args(Version, Hostname,Port); + _ -> + openssl_client_args(Version, Hostname, Port, SNIHostname) + end, + ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), + + ssl_test_lib:check_result(Server, ExpectedSNIHostname), + ssl_test_lib:close_port(ClientPort), + ssl_test_lib:close(Server), + ok. + + +erlang_server_openssl_client_sni_test_sni_fun(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + Version = ssl_test_lib:protocol_version(Config), + ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", + [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + [{sni_hosts, ServerSNIConf}] = proplists:get_value(sni_server_opts, Config), + SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end, + ServerOptions = proplists:get_value(server_rsa_opts, Config) ++ [{sni_fun, SNIFun}], + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Exe = "openssl", + ClientArgs = case SNIHostname of + undefined -> + openssl_client_args(Version, Hostname,Port); + _ -> + openssl_client_args(Version, Hostname, Port, SNIHostname) + end, + + ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), + + ssl_test_lib:check_result(Server, ExpectedSNIHostname), + ssl_test_lib:close_port(ClientPort), + ssl_test_lib:close(Server). + +send_and_hostname(SSLSocket) -> + ssl:send(SSLSocket, "OK"), + case ssl:connection_information(SSLSocket, [sni_hostname]) of + {ok, []} -> + undefined; + {ok, [{sni_hostname, Hostname}]} -> + Hostname + end. + +openssl_client_args(Version, Hostname, Port) -> + ["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)]. + +openssl_client_args(Version, Hostname, Port, ServerName) -> + ["s_client", "-connect", Hostname ++ ":" ++ + integer_to_list(Port), ssl_test_lib:version_flag(Version), "-servername", ServerName]. + +check_openssl_sni_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case ssl_test_lib:is_sane_oppenssl_client() of + true -> + case string:str(HelpText, "-servername") of + 0 -> + {skip, "Current openssl doesn't support SNI"}; + _ -> + Config + end; + false -> + {skip, "Current openssl doesn't support SNI or extension handling is flawed"} + end. diff --git a/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl b/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl new file mode 100644 index 0000000000..8a2692ec1d --- /dev/null +++ b/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl @@ -0,0 +1,172 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(openssl_tls_1_3_version_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + %%{group, openssl_server}, + {group, openssl_client} + ]. + +groups() -> + [ + %%{openssl_server, [{group, 'tlsv1.3'}]}, + {openssl_client, [{group, 'tlsv1.3'}]}, + {'tlsv1.3', [], cert_groups()}, + {rsa, [], tests()}, + {ecdsa, [], tests()} + ]. + +cert_groups() -> + [{group, rsa}, + {group, ecdsa}]. + +tests() -> + [tls13_client_tls12_server, + %%tls13_client_with_ext_tls12_server, + tls12_client_tls13_server]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + case ssl_test_lib:check_sane_openssl_version('tlsv1.3') of + true -> + ssl_test_lib:clean_start(), + Config; + false -> + {skip, openssl_does_not_support_version} + end + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(openssl_client, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, openssl}, {server_type, erlang} | Config]; +init_per_group(openssl_server, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, erlang}, {server_type, openssl} | Config]; +init_per_group(rsa, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; +init_per_group(ecdsa, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso + (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso + ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +tls13_client_tls12_server() -> + [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}]. + +tls13_client_tls12_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%% tls13_client_with_ext_tls12_server() -> +%% [{doc,"Test basic connection between TLS 1.2 server and TLS 1.3 client when " +%% "client has TLS 1.3 specsific extensions"}]. + +%% tls13_client_with_ext_tls12_server(Config) -> +%% ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), +%% ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + +%% {ServerOpts, ClientOpts} = +%% case proplists:get_value(client_type) of +%% erlang -> +%% {[{versions, ['tlsv1.2']}|ServerOpts0], +%% [{versions, ['tlsv1.2','tlsv1.3']}, +%% {signature_algs_cert, [ecdsa_secp384r1_sha384, +%% ecdsa_secp256r1_sha256, +%% rsa_pss_rsae_sha256, +%% rsa_pkcs1_sha256, +%% {sha256,rsa},{sha256,dsa}]}|ClientOpts0]}; +%% openssl -> + + +%% ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +tls12_client_tls13_server() -> + [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}]. + +tls12_client_tls13_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + diff --git a/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl b/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl new file mode 100644 index 0000000000..f225065ba6 --- /dev/null +++ b/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl @@ -0,0 +1,96 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. 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_eqc_cipher_format). + +-compile(export_all). + +-proptest(eqc). +-proptest([triq,proper]). + +-ifndef(EQC). +-ifndef(PROPER). +-ifndef(TRIQ). +-define(EQC,true). +-endif. +-endif. +-endif. + +-ifdef(EQC). +-include_lib("eqc/include/eqc.hrl"). +-define(MOD_eqc,eqc). + +-else. +-ifdef(PROPER). +-include_lib("proper/include/proper.hrl"). +-define(MOD_eqc,proper). + +-else. +-ifdef(TRIQ). +-define(MOD_eqc,triq). +-include_lib("triq/include/triq.hrl"). + +-endif. +-endif. +-endif. + +-define('TLS_v1.3', 'tlsv1.3'). +-define('TLS_v1.2', 'tlsv1.2'). +-define('TLS_v1.1', 'tlsv1.1'). +-define('TLS_v1', 'tlsv1'). +-define('SSL_v3', 'sslv3'). + +%%-------------------------------------------------------------------- +%% Properties -------------------------------------------------------- +%%-------------------------------------------------------------------- + +prop_tls_cipher_suite_rfc_name() -> + ?FORALL({CipherSuite, TLSVersion}, ?LET(Version, tls_version(), {cipher_suite(Version), Version}), + case ssl:str_to_suite(ssl:suite_to_str(CipherSuite)) of + CipherSuite -> + true; + _ -> + false + end + ). + +prop_tls_cipher_suite_openssl_name() -> + ?FORALL({CipherSuite, TLSVersion}, ?LET(Version, tls_version(), {cipher_suite(Version), Version}), + case ssl:str_to_suite(ssl:suite_to_openssl_str(CipherSuite)) of + CipherSuite -> + true; + _ -> + false + end + ). + + +%%-------------------------------------------------------------------- +%% Generators ----------------------------------------------- +%%-------------------------------------------------------------------- +tls_version() -> + oneof([?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1', ?'SSL_v3']). + +cipher_suite(Version) -> + oneof(cipher_suites(Version)). + +cipher_suites(Version) -> + ssl:cipher_suites(all, Version). + diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl new file mode 100644 index 0000000000..2ceb540e15 --- /dev/null +++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl @@ -0,0 +1,883 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. 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_eqc_handshake). + +-compile(export_all). + +-proptest(eqc). +-proptest([triq,proper]). + +-ifndef(EQC). +-ifndef(PROPER). +-ifndef(TRIQ). +-define(EQC,true). +-endif. +-endif. +-endif. + +-ifdef(EQC). +-include_lib("eqc/include/eqc.hrl"). +-define(MOD_eqc,eqc). + +-else. +-ifdef(PROPER). +-include_lib("proper/include/proper.hrl"). +-define(MOD_eqc,proper). + +-else. +-ifdef(TRIQ). +-define(MOD_eqc,triq). +-include_lib("triq/include/triq.hrl"). + +-endif. +-endif. +-endif. + +-include_lib("kernel/include/inet.hrl"). +-include_lib("ssl/src/tls_handshake_1_3.hrl"). +-include_lib("ssl/src/tls_handshake.hrl"). +-include_lib("ssl/src/ssl_handshake.hrl"). +-include_lib("ssl/src/ssl_alert.hrl"). +-include_lib("ssl/src/ssl_internal.hrl"). + +-define('TLS_v1.3', {3,4}). +-define('TLS_v1.2', {3,3}). +-define('TLS_v1.1', {3,2}). +-define('TLS_v1', {3,1}). +-define('SSL_v3', {3,0}). + +%%-------------------------------------------------------------------- +%% Properties -------------------------------------------------------- +%%-------------------------------------------------------------------- + +prop_tls_hs_encode_decode() -> + ?FORALL({Handshake, TLSVersion}, ?LET(Version, tls_version(), {tls_msg(Version), Version}), + try + [Type, _Length, Data] = tls_handshake:encode_handshake(Handshake, TLSVersion), + case tls_handshake:decode_handshake(TLSVersion, Type, Data) of + Handshake -> + true; + _ -> + false + end + catch + throw:#alert{} -> + true + end + ). + +%%-------------------------------------------------------------------- +%% Message Generators ----------------------------------------------- +%%-------------------------------------------------------------------- + +tls_msg(?'TLS_v1.3'= Version) -> + oneof([client_hello(Version), + server_hello(Version), + %%new_session_ticket() + #end_of_early_data{}, + encrypted_extensions(), + certificate_1_3(), + %%certificate_request_1_3, + certificate_verify_1_3(), + finished(), + key_update() + ]); +tls_msg(Version) -> + oneof([ + #hello_request{}, + client_hello(Version), + server_hello(Version), + certificate(), + %%server_key_exchange() + certificate_request(Version), + #server_hello_done{}, + %%certificate_verify() + %%client_key_exchange() + finished() + ]). + +%% +%% Shared messages +%% +client_hello(?'TLS_v1.3' = Version) -> + #client_hello{session_id = session_id(), + client_version = ?'TLS_v1.2', + cipher_suites = cipher_suites(Version), + compression_methods = compressions(Version), + random = client_random(Version), + extensions = client_hello_extensions(Version) + }; +client_hello(Version) -> + #client_hello{session_id = session_id(), + client_version = Version, + cipher_suites = cipher_suites(Version), + compression_methods = compressions(Version), + random = client_random(Version), + extensions = client_hello_extensions(Version) + }; +client_hello(?'SSL_v3' = Version) -> + #client_hello{session_id = session_id(), + client_version = Version, + cipher_suites = cipher_suites(Version), + compression_methods = compressions(Version), + random = client_random(Version), + extensions = ssl_handshake:empty_extensions(Version, client_hello) + }. + +server_hello(?'TLS_v1.3' = Version) -> + #server_hello{server_version = ?'TLS_v1.2', + session_id = session_id(), + random = server_random(Version), + cipher_suite = cipher_suite(Version), + compression_method = compression(Version), + extensions = server_hello_extensions(Version) + }; +server_hello(?'SSL_v3' = Version) -> + #server_hello{server_version = Version, + session_id = session_id(), + random = server_random(Version), + cipher_suite = cipher_suite(Version), + compression_method = compression(Version), + extensions = ssl_handshake:empty_extensions(Version, server_hello) + }; +server_hello(Version) -> + #server_hello{server_version = Version, + session_id = session_id(), + random = server_random(Version), + cipher_suite = cipher_suite(Version), + compression_method = compression(Version), + extensions = server_hello_extensions(Version) + }. + +certificate() -> + #certificate{ + asn1_certificates = certificate_chain() + }. + +certificate_1_3() -> + ?LET(Certs, certificate_chain(), + #certificate_1_3{ + certificate_request_context = certificate_request_context(), + certificate_list = certificate_entries(Certs, []) + }). + +certificate_verify_1_3() -> + ?LET(Certs, certificate_chain(), + #certificate_verify_1_3{ + algorithm = sig_scheme(), + signature = signature() + }). + +finished() -> + ?LET(Size, digest_size(), + #finished{verify_data = crypto:strong_rand_bytes(Size)}). + +%% +%% TLS 1.0-1.2 messages +%% + + + +%% +%% TLS 1.3 messages +%% + +encrypted_extensions() -> + ?LET(Exts, extensions(?'TLS_v1.3', encrypted_extensions), + #encrypted_extensions{extensions = Exts}). + + +key_update() -> + #key_update{request_update = request_update()}. + + +%%-------------------------------------------------------------------- +%% Messge Data Generators ------------------------------------------- +%%-------------------------------------------------------------------- + +tls_version() -> + oneof([?'TLS_v1.3', ?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1', ?'SSL_v3']). + +cipher_suite(Version) -> + oneof(cipher_suites(Version)). + +cipher_suites(Version) -> + ssl_cipher:suites(Version). + +session_id() -> + crypto:strong_rand_bytes(?NUM_OF_SESSION_ID_BYTES). + +compression(Version) -> + oneof(compressions(Version)). + +compressions(_) -> + ssl_record:compressions(). + +client_random(_) -> + crypto:strong_rand_bytes(32). + +server_random(_) -> + crypto:strong_rand_bytes(32). + + +client_hello_extensions(Version) -> + ?LET(Exts, extensions(Version, client_hello), + maps:merge(ssl_handshake:empty_extensions(Version, client_hello), + Exts)). + +server_hello_extensions(Version) -> + ?LET(Exts, extensions(Version, server_hello), + maps:merge(ssl_handshake:empty_extensions(Version, server_hello), + Exts)). + +key_share_client_hello() -> + oneof([undefined]). + %%oneof([#key_share_client_hello{}, undefined]). + +key_share_server_hello() -> + oneof([undefined]). + %%oneof([#key_share_server_hello{}, undefined]). + +pre_shared_keyextension() -> + oneof([undefined]). + %%oneof([#pre_shared_keyextension{},undefined]). + +%% +--------------------------------------------------+-------------+ +%% | Extension | TLS 1.3 | +%% +--------------------------------------------------+-------------+ +%% | server_name [RFC6066] | CH, EE | +%% | | | +%% | max_fragment_length [RFC6066] | CH, EE | +%% | | | +%% | status_request [RFC6066] | CH, CR, CT | +%% | | | +%% | supported_groups [RFC7919] | CH, EE | +%% | | | +%% | signature_algorithms (RFC 8446) | CH, CR | +%% | | | +%% | use_srtp [RFC5764] | CH, EE | +%% | | | +%% | heartbeat [RFC6520] | CH, EE | +%% | | | +%% | application_layer_protocol_negotiation [RFC7301] | CH, EE | +%% | | | +%% | signed_certificate_timestamp [RFC6962] | CH, CR, CT | +%% | | | +%% | client_certificate_type [RFC7250] | CH, EE | +%% | | | +%% | server_certificate_type [RFC7250] | CH, EE | +%% | | | +%% | padding [RFC7685] | CH | +%% | | | +%% | key_share (RFC 8446) | CH, SH, HRR | +%% | | | +%% | pre_shared_key (RFC 8446) | CH, SH | +%% | | | +%% | psk_key_exchange_modes (RFC 8446) | CH | +%% | | | +%% | early_data (RFC 8446) | CH, EE, NST | +%% | | | +%% | cookie (RFC 8446) | CH, HRR | +%% | | | +%% | supported_versions (RFC 8446) | CH, SH, HRR | +%% | | | +%% | certificate_authorities (RFC 8446) | CH, CR | +%% | | | +%% | oid_filters (RFC 8446) | CR | +%% | | | +%% | post_handshake_auth (RFC 8446) | CH | +%% | | | +%% | signature_algorithms_cert (RFC 8446) | CH, CR | +%% +--------------------------------------------------+-------------+ +extensions(?'TLS_v1.3' = Version, MsgType = client_hello) -> + ?LET({ + ServerName, + %% MaxFragmentLength, + %% StatusRequest, + SupportedGroups, + SignatureAlgorithms, + %% UseSrtp, + %% Heartbeat, + ALPN, + %% SignedCertTimestamp, + %% ClientCertiticateType, + %% ServerCertificateType, + %% Padding, + KeyShare, + PreSharedKey, + PSKKeyExchangeModes, + %% EarlyData, + %% Cookie, + SupportedVersions, + %% CertAuthorities, + %% PostHandshakeAuth, + SignatureAlgorithmsCert + }, + { + oneof([server_name(), undefined]), + %% oneof([max_fragment_length(), undefined]), + %% oneof([status_request(), undefined]), + oneof([supported_groups(Version), undefined]), + oneof([signature_algs(Version), undefined]), + %% oneof([use_srtp(), undefined]), + %% oneof([heartbeat(), undefined]), + oneof([alpn(), undefined]), + %% oneof([signed_cert_timestamp(), undefined]), + %% oneof([client_cert_type(), undefined]), + %% oneof([server_cert_type(), undefined]), + %% oneof([padding(), undefined]), + oneof([key_share(MsgType), undefined]), + oneof([pre_shared_key(MsgType), undefined]), + oneof([psk_key_exchange_modes(), undefined]), + %% oneof([early_data(), undefined]), + %% oneof([cookie(), undefined]), + oneof([client_hello_versions(Version)]), + %% oneof([cert_authorities(), undefined]), + %% oneof([post_handshake_auth(), undefined]), + oneof([signature_algs_cert(), undefined]) + }, + maps:filter(fun(_, undefined) -> + false; + (_,_) -> + true + end, + #{ + sni => ServerName, + %% max_fragment_length => MaxFragmentLength, + %% status_request => StatusRequest, + elliptic_curves => SupportedGroups, + signature_algs => SignatureAlgorithms, + %% use_srtp => UseSrtp, + %% heartbeat => Heartbeat, + alpn => ALPN, + %% signed_cert_timestamp => SignedCertTimestamp, + %% client_cert_type => ClientCertificateType, + %% server_cert_type => ServerCertificateType, + %% padding => Padding, + key_share => KeyShare, + pre_shared_key => PreSharedKey, + psk_key_exchange_modes => PSKKeyExchangeModes, + %% early_data => EarlyData, + %% cookie => Cookie, + client_hello_versions => SupportedVersions, + %% cert_authorities => CertAuthorities, + %% post_handshake_auth => PostHandshakeAuth, + signature_algs_cert => SignatureAlgorithmsCert + })); +extensions(?'SSL_v3', client_hello) -> + #{}; +extensions(Version, client_hello) -> + ?LET({ + SNI, + ECPoitF, + ECCurves, + ALPN, + NextP, + SRP + %% RenegotiationInfo + }, + { + oneof([sni(), undefined]), + oneof([ec_point_formats(), undefined]), + oneof([elliptic_curves(Version), undefined]), + oneof([alpn(), undefined]), + oneof([next_protocol_negotiation(), undefined]), + oneof([srp(), undefined]) + %% oneof([renegotiation_info(), undefined]) + }, + maps:filter(fun(_, undefined) -> + false; + (_,_) -> + true + end, + #{ + sni => SNI, + ec_point_formats => ECPoitF, + elliptic_curves => ECCurves, + alpn => ALPN, + next_protocol_negotiation => NextP, + srp => SRP + %% renegotiation_info => RenegotiationInfo + })); +extensions(?'TLS_v1.3' = Version, MsgType = server_hello) -> + ?LET({ + KeyShare, + PreSharedKey, + SupportedVersions + }, + { + oneof([key_share(MsgType), undefined]), + oneof([pre_shared_key(MsgType), undefined]), + oneof([server_hello_selected_version()]) + }, + maps:filter(fun(_, undefined) -> + false; + (_,_) -> + true + end, + #{ + key_share => KeyShare, + pre_shared_key => PreSharedKey, + server_hello_selected_version => SupportedVersions + })); +extensions(Version, server_hello) -> + ?LET({ + ECPoitF, + ALPN, + NextP + %% RenegotiationInfo, + }, + { + oneof([ec_point_formats(), undefined]), + oneof([alpn(), undefined]), + oneof([next_protocol_negotiation(), undefined]) + %% oneof([renegotiation_info(), undefined]), + }, + maps:filter(fun(_, undefined) -> + false; + (_,_) -> + true + end, + #{ + ec_point_formats => ECPoitF, + alpn => ALPN, + next_protocol_negotiation => NextP + %% renegotiation_info => RenegotiationInfo + })); +extensions(?'TLS_v1.3' = Version, encrypted_extensions) -> + ?LET({ + ServerName, + %% MaxFragmentLength, + SupportedGroups, + %% UseSrtp, + %% Heartbeat, + ALPN + %% ClientCertiticateType, + %% ServerCertificateType, + %% EarlyData + }, + { + oneof([server_name(), undefined]), + %% oneof([max_fragment_length(), undefined]), + oneof([supported_groups(Version), undefined]), + %% oneof([use_srtp(), undefined]), + %% oneof([heartbeat(), undefined]), + oneof([alpn(), undefined]) + %% oneof([client_cert_type(), undefined]), + %% oneof([server_cert_type(), undefined]), + %% oneof([early_data(), undefined]) + }, + maps:filter(fun(_, undefined) -> + false; + (_,_) -> + true + end, + #{ + sni => ServerName, + %% max_fragment_length => MaxFragmentLength, + elliptic_curves => SupportedGroups, + %% use_srtp => UseSrtp, + %% heartbeat => Heartbeat, + alpn => ALPN + %% client_cert_type => ClientCertificateType, + %% server_cert_type => ServerCertificateType, + %% early_data => EarlyData + })). + +server_name() -> + ?LET(ServerName, sni(), + ServerName). + %% sni(). + +signature_algs_cert() -> + ?LET(List, sig_scheme_list(), + #signature_algorithms_cert{signature_scheme_list = List}). + +signature_algorithms() -> + ?LET(List, sig_scheme_list(), + #signature_algorithms{signature_scheme_list = List}). + +sig_scheme_list() -> + oneof([[rsa_pkcs1_sha256], + [rsa_pkcs1_sha256, ecdsa_sha1], + [rsa_pkcs1_sha256, + rsa_pkcs1_sha384, + rsa_pkcs1_sha512, + ecdsa_secp256r1_sha256, + ecdsa_secp384r1_sha384, + ecdsa_secp521r1_sha512, + rsa_pss_rsae_sha256, + rsa_pss_rsae_sha384, + rsa_pss_rsae_sha512, + rsa_pss_pss_sha256, + rsa_pss_pss_sha384, + rsa_pss_pss_sha512, + rsa_pkcs1_sha1, + ecdsa_sha1] + ]). + +sig_scheme() -> + oneof([rsa_pkcs1_sha256, + rsa_pkcs1_sha384, + rsa_pkcs1_sha512, + ecdsa_secp256r1_sha256, + ecdsa_secp384r1_sha384, + ecdsa_secp521r1_sha512, + rsa_pss_rsae_sha256, + rsa_pss_rsae_sha384, + rsa_pss_rsae_sha512, + rsa_pss_pss_sha256, + rsa_pss_pss_sha384, + rsa_pss_pss_sha512, + rsa_pkcs1_sha1, + ecdsa_sha1]). + +signature() -> + <<44,119,215,137,54,84,156,26,121,212,64,173,189,226, + 191,46,76,89,204,2,78,79,163,228,90,21,89,179,4,198, + 109,14,52,26,230,22,56,8,170,129,86,0,7,132,245,81, + 181,131,62,70,79,167,112,85,14,171,175,162,110,29, + 212,198,45,188,83,176,251,197,224,104,95,74,89,59, + 26,60,63,79,238,196,137,65,23,199,127,145,176,184, + 216,3,48,116,172,106,97,83,227,172,246,137,91,79, + 173,119,169,60,67,1,177,117,9,93,38,86,232,253,73, + 140,17,147,130,110,136,245,73,10,91,70,105,53,225, + 158,107,60,190,30,14,26,92,147,221,60,117,104,53,70, + 142,204,7,131,11,183,192,120,246,243,68,99,147,183, + 49,149,48,188,8,218,17,150,220,121,2,99,194,140,35, + 13,249,201,37,216,68,45,87,58,18,10,106,11,132,241, + 71,170,225,216,197,212,29,107,36,80,189,184,202,56, + 86,213,45,70,34,74,71,48,137,79,212,194,172,151,57, + 57,30,126,24,157,198,101,220,84,162,89,105,185,245, + 76,105,212,176,25,6,148,49,194,106,253,241,212,200, + 37,154,227,53,49,216,72,82,163>>. + +client_hello_versions(?'TLS_v1.3') -> + ?LET(SupportedVersions, + oneof([[{3,4}], + %% This list breaks the property but can be used for negative tests + %% [{3,3},{3,4}], + [{3,4},{3,3}], + [{3,4},{3,3},{3,2},{3,1},{3,0}] + ]), + #client_hello_versions{versions = SupportedVersions}); +client_hello_versions(_) -> + ?LET(SupportedVersions, + oneof([[{3,3}], + [{3,3},{3,2}], + [{3,3},{3,2},{3,1},{3,0}] + ]), + #client_hello_versions{versions = SupportedVersions}). + +server_hello_selected_version() -> + #server_hello_selected_version{selected_version = {3,4}}. + +request_update() -> + oneof([?UPDATE_NOT_REQUESTED, ?UPDATE_REQUESTED]). + +certificate_chain()-> + Conf = cert_conf(), + ?LET(Chain, + choose_certificate_chain(Conf), + Chain). + +choose_certificate_chain(#{server_config := ServerConf, + client_config := ClientConf}) -> + oneof([certificate_chain(ServerConf), certificate_chain(ClientConf)]). + +certificate_request_context() -> + oneof([<<>>, + <<1>>, + <<"foobar">> + ]). +certificate_entries([], Acc) -> + lists:reverse(Acc); +certificate_entries([Cert | Rest], Acc) -> + certificate_entries(Rest, [certificate_entry(Cert) | Acc]). + +certificate_entry(Cert) -> + #certificate_entry{data = Cert, + extensions = certificate_entry_extensions() + }. +certificate_entry_extensions() -> + #{}. + +certificate_chain(Conf) -> + CAs = proplists:get_value(cacerts, Conf), + Cert = proplists:get_value(cert, Conf), + %% Middle argument are of correct type but will not be used + {ok, _, Chain} = ssl_certificate:certificate_chain(Cert, ets:new(foo, []), make_ref(), CAs), + Chain. + +cert_conf()-> + Hostname = net_adm:localhost(), + {ok, #hostent{h_addr_list = [_IP |_]}} = inet:gethostbyname(net_adm:localhost()), + public_key:pkix_test_data(#{server_chain => + #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]], + peer => [{extensions, [#'Extension'{extnID = + ?'id-ce-subjectAltName', + extnValue = [{dNSName, Hostname}], + critical = false}]}, + {key, ssl_test_lib:hardcode_rsa_key(3)} + ]}, + client_chain => + #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]], + peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}). + +certificate_request(Version) -> + #certificate_request{certificate_types = certificate_types(Version), + hashsign_algorithms = hashsign_algorithms(Version), + certificate_authorities = certificate_authorities()}. + +certificate_types(?'TLS_v1.3') -> + iolist_to_binary([<<?BYTE(?ECDSA_SIGN)>>, <<?BYTE(?RSA_SIGN)>>]); +certificate_types(?'TLS_v1.2') -> + iolist_to_binary([<<?BYTE(?ECDSA_SIGN)>>, <<?BYTE(?RSA_SIGN)>>, <<?BYTE(?DSS_SIGN)>>]); +certificate_types(_) -> + iolist_to_binary([<<?BYTE(?ECDSA_SIGN)>>, <<?BYTE(?RSA_SIGN)>>, <<?BYTE(?DSS_SIGN)>>]). + + + +signature_algs({3,4}) -> + ?LET(Algs, signature_algorithms(), + Algs); +signature_algs({3,3} = Version) -> + #hash_sign_algos{hash_sign_algos = hash_alg_list(Version)}; +signature_algs(Version) when Version < {3,3} -> + undefined. + + + +hashsign_algorithms({_, N} = Version) when N >= 3 -> + #hash_sign_algos{hash_sign_algos = hash_alg_list(Version)}; +hashsign_algorithms(_) -> + undefined. + +hash_alg_list(Version) -> + ?LET(NumOf, choose(1,15), + ?LET(List, [hash_alg(Version) || _ <- lists:seq(1,NumOf)], + lists:usort(List) + )). + +hash_alg(Version) -> + ?LET(Alg, sign_algorithm(Version), + {hash_algorithm(Version, Alg), Alg} + ). + +hash_algorithm(?'TLS_v1.3', _) -> + oneof([sha, sha224, sha256, sha384, sha512]); +hash_algorithm(?'TLS_v1.2', rsa) -> + oneof([sha, sha224, sha256, sha384, sha512]); +hash_algorithm(_, rsa) -> + oneof([md5, sha, sha224, sha256, sha384, sha512]); +hash_algorithm(_, ecdsa) -> + oneof([sha, sha224, sha256, sha384, sha512]); +hash_algorithm(_, dsa) -> + sha. + +sign_algorithm(?'TLS_v1.3') -> + oneof([rsa, ecdsa]); +sign_algorithm(_) -> + oneof([rsa, dsa, ecdsa]). + +certificate_authorities() -> + #{server_config := ServerConf} = cert_conf(), + Authorities = proplists:get_value(cacerts, ServerConf), + Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) -> + OTPSubj = TBSCert#'OTPTBSCertificate'.subject, + DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp), + DNEncodedLen = byte_size(DNEncodedBin), + <<?UINT16(DNEncodedLen), DNEncodedBin/binary>> + end, + list_to_binary([Enc(public_key:pkix_decode_cert(DERCert, otp)) || DERCert <- Authorities]). + +digest_size()-> + oneof([160,224,256,384,512]). + +key_share_entry() -> + undefined. + %%#key_share_entry{}. + +server_hello_selected_version(Version) -> + #server_hello_selected_version{selected_version = Version}. + +sni() -> + #sni{hostname = net_adm:localhost()}. + +ec_point_formats() -> + #ec_point_formats{ec_point_format_list = ec_point_format_list()}. + +ec_point_format_list() -> + [?ECPOINT_UNCOMPRESSED]. + +elliptic_curves({_, Minor}) when Minor < 4 -> + Curves = tls_v1:ecc_curves(Minor), + #elliptic_curves{elliptic_curve_list = Curves}. + +%% RFC 8446 (TLS 1.3) renamed the "elliptic_curve" extension. +supported_groups({_, Minor}) when Minor >= 4 -> + SupportedGroups = tls_v1:groups(Minor), + #supported_groups{supported_groups = SupportedGroups}. + + +alpn() -> + ?LET(ExtD, alpn_protocols(), #alpn{extension_data = ExtD}). + +alpn_protocols() -> + oneof([<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>, <<"http/1.0">>, <<"http/1.1">>]). + +next_protocol_negotiation() -> + %% Predecessor to APLN + ?LET(ExtD, alpn_protocols(), #next_protocol_negotiation{extension_data = ExtD}). + +srp() -> + ?LET(Name, gen_name(), #srp{username = list_to_binary(Name)}). + +renegotiation_info() -> + #renegotiation_info{renegotiated_connection = 0}. + +gen_name() -> + ?LET(Size, choose(1,10), gen_string(Size)). + +gen_char() -> + choose($a,$z). + +gen_string(N) -> + gen_string(N, []). + +gen_string(0, Acc) -> + Acc; +gen_string(N, Acc) -> + ?LET(Char, gen_char(), gen_string(N-1, [Char | Acc])). + +key_share(client_hello) -> + ?LET(ClientShares, key_share_entry_list(), + #key_share_client_hello{ + client_shares = ClientShares}); +key_share(server_hello) -> + ?LET([ServerShare], key_share_entry_list(1), + #key_share_server_hello{ + server_share = ServerShare}). + +key_share_entry_list() -> + Max = length(ssl:groups()), + ?LET(Size, choose(1,Max), key_share_entry_list(Size)). +%% +key_share_entry_list(N) -> + key_share_entry_list(N, ssl:groups(), []). +%% +key_share_entry_list(0, _Pool, Acc) -> + Acc; +key_share_entry_list(N, Pool, Acc) -> + R = rand:uniform(length(Pool)), + G = lists:nth(R, Pool), + P = generate_public_key(G), + KeyShareEntry = + #key_share_entry{ + group = G, + key_exchange = P}, + key_share_entry_list(N - 1, Pool -- [G], [KeyShareEntry|Acc]). + +%% TODO: fix curve generation +generate_public_key(Group) + when Group =:= secp256r1 orelse + Group =:= secp384r1 orelse + Group =:= secp521r1 orelse + Group =:= x448 orelse + Group =:= x25519 -> + #'ECPrivateKey'{publicKey = PublicKey} = + public_key:generate_key({namedCurve, secp256r1}), + PublicKey; +generate_public_key(Group) -> + {PublicKey, _} = + public_key:generate_key(ssl_dh_groups:dh_params(Group)), + PublicKey. + +groups() -> + Max = length(ssl:groups()), + ?LET(Size, choose(1,Max), group_list(Size)). + +group_list(N) -> + group_list(N, ssl:groups(), []). +%% +group_list(0, _Pool, Acc) -> + Acc; +group_list(N, Pool, Acc) -> + R = rand:uniform(length(Pool)), + G = lists:nth(R, Pool), + group_list(N - 1, Pool -- [G], [G|Acc]). + + +ke_modes() -> + oneof([[psk_ke],[psk_dhe_ke],[psk_ke,psk_dhe_ke]]). + +psk_key_exchange_modes() -> + ?LET(KEModes, ke_modes(), + #psk_key_exchange_modes{ + ke_modes = KEModes}). + +pre_shared_key(client_hello) -> + ?LET(OfferedPsks, offered_psks(), + #pre_shared_key_client_hello{ + offered_psks = OfferedPsks}); +pre_shared_key(server_hello) -> + ?LET(SelectedIdentity, selected_identity(), + #pre_shared_key_server_hello{ + selected_identity = SelectedIdentity}). + +selected_identity() -> + rand:uniform(32). + +offered_psks() -> + ?LET(Size, choose(1,5), + #offered_psks{ + identities = psk_identities(Size), + binders = psk_binders(Size)}). + +psk_identities(Size) -> + psk_identities(Size, []). +%% +psk_identities(0, Acc) -> + Acc; +psk_identities(N, Acc) -> + psk_identities(N - 1, [psk_identity()|Acc]). + +psk_identity() -> + Len = rand:uniform(32), + Identity = crypto:strong_rand_bytes(Len), + Age = crypto:strong_rand_bytes(4), + #psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}. + +psk_binders(Size) -> + psk_binders(Size, []). +%% +psk_binders(0, Acc) -> + Acc; +psk_binders(N, Acc) -> + psk_binders(N - 1, [psk_binder()|Acc]). + +psk_binder() -> + Len = rand:uniform(224) + 31, + crypto:strong_rand_bytes(Len). diff --git a/lib/ssl/test/ssl.spec b/lib/ssl/test/ssl.spec index cb54168d36..15587abecd 100644 --- a/lib/ssl/test/ssl.spec +++ b/lib/ssl/test/ssl.spec @@ -1,4 +1,12 @@ -{suites,"../ssl_test",all}. -{skip_suites, "../ssl_test", - [ssl_bench_SUITE, ssl_dist_bench_SUITE], - "Benchmarks run separately"}. +% {merge_tests,false}. +{alias,dir,"../ssl_test"}. + +{suites,dir,all}. +{skip_groups,dir,ssl_bench_SUITE,setup,"Benchmarks run separately"}. +{skip_groups,dir,ssl_bench_SUITE,payload,"Benchmarks run separately"}. +{skip_groups,dir,ssl_bench_SUITE,pem_cache,"Benchmarks run separately"}. +{skip_groups,dir,ssl_dist_bench_SUITE,setup,"Benchmarks run separately"}. +{skip_groups,dir,ssl_dist_bench_SUITE,roundtrip,"Benchmarks run separately"}. +{skip_groups,dir,ssl_dist_bench_SUITE,throughput,"Benchmarks run separately"}. +{skip_groups,dir,ssl_dist_bench_SUITE,sched_utilization,"Benchmarks run separately"}. + diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index c64358960c..d02888793c 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -51,35 +51,7 @@ groups() -> ]. test_cases()-> - key_cert_combinations() - ++ misc() - ++ ecc_negotiation(). - -key_cert_combinations() -> - server_ecdh_rsa() ++ - server_ecdhe_rsa() ++ - server_ecdh_ecdsa() ++ - server_ecdhe_ecdsa(). - -server_ecdh_rsa() -> - [client_ecdh_rsa_server_ecdh_rsa, - client_ecdhe_rsa_server_ecdh_rsa, - client_ecdhe_ecdsa_server_ecdh_rsa]. - -server_ecdhe_rsa() -> - [client_ecdh_rsa_server_ecdhe_rsa, - client_ecdhe_rsa_server_ecdhe_rsa, - client_ecdhe_ecdsa_server_ecdhe_rsa]. - -server_ecdh_ecdsa() -> - [client_ecdh_ecdsa_server_ecdh_ecdsa, - client_ecdhe_rsa_server_ecdh_ecdsa, - client_ecdhe_ecdsa_server_ecdh_ecdsa]. - -server_ecdhe_ecdsa() -> - [client_ecdh_rsa_server_ecdhe_ecdsa, - client_ecdh_ecdsa_server_ecdhe_ecdsa, - client_ecdhe_ecdsa_server_ecdhe_ecdsa]. + misc() ++ ecc_negotiation(). misc()-> [client_ecdsa_server_ecdsa_with_raw_key]. @@ -160,35 +132,6 @@ end_per_testcase(_TestCase, Config) -> %% Test diffrent certificate chain types, note that it is the servers %% chain that affect what cipher suit that will be choosen -%% ECDH_RSA -client_ecdh_rsa_server_ecdh_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdh_rsa_server_ecdh_rsa(Config). -client_ecdhe_rsa_server_ecdh_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_rsa_server_ecdh_rsa(Config). -client_ecdhe_ecdsa_server_ecdh_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_ecdsa_server_ecdh_rsa(Config). -%% ECDHE_RSA -client_ecdh_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdh_rsa_server_ecdhe_rsa(Config). -client_ecdhe_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_rsa_server_ecdhe_rsa(Config). -client_ecdhe_ecdsa_server_ecdhe_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_ecdsa_server_ecdhe_rsa(Config). -%% ECDH_ECDSA -client_ecdh_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdh_ecdsa_server_ecdh_ecdsa(Config). -client_ecdhe_rsa_server_ecdh_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_rsa_server_ecdh_ecdsa(Config). -client_ecdhe_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_ecdsa_server_ecdh_ecdsa(Config). -%% ECDHE_ECDSA -client_ecdh_rsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdh_rsa_server_ecdhe_ecdsa(Config). -client_ecdh_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdh_ecdsa_server_ecdhe_ecdsa(Config). -client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config). - client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, diff --git a/lib/ssl/test/ssl_ECC_openssl_SUITE.erl b/lib/ssl/test/ssl_ECC_openssl_SUITE.erl index 81a7dfd2da..787c08a517 100644 --- a/lib/ssl/test/ssl_ECC_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_openssl_SUITE.erl @@ -33,77 +33,23 @@ %%-------------------------------------------------------------------- all() -> - case test_cases() of - [_|_] -> - all_groups(); - [] -> - [skip] - end. - -all_groups() -> case ssl_test_lib:openssl_sane_dtls() of true -> [{group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'dtlsv1.2'}, - {group, 'dtlsv1'}]; + {group, 'dtlsv1.2'}]; false -> - [{group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}] + [{group, 'tlsv1.2'}] end. groups() -> case ssl_test_lib:openssl_sane_dtls() of true -> - [{'tlsv1.2', [], [mix_sign | test_cases()]}, - {'tlsv1.1', [], test_cases()}, - {'tlsv1', [], test_cases()}, - {'dtlsv1.2', [], [mix_sign | test_cases()]}, - {'dtlsv1', [], test_cases()}]; + [{'tlsv1.2', [], [mix_sign]}, + {'dtlsv1.2', [], [mix_sign]}]; false -> - [{'tlsv1.2', [], [mix_sign | test_cases()]}, - {'tlsv1.1', [], test_cases()}, - {'tlsv1', [], test_cases()}] + [{'tlsv1.2', [], [mix_sign]}] end. - -test_cases()-> - cert_combinations(). -cert_combinations() -> - lists:append(lists:map(fun({Name, Suites}) -> - case ssl_test_lib:openssl_filter(Name) of - [] -> - []; - [_|_] -> - Suites - end - end, [{"ECDH-ECDSA", server_ecdh_ecdsa()}, - {"ECDH-RSA", server_ecdh_rsa()}, - {"ECDHE-RSA", server_ecdhe_rsa()}, - {"ECDHE-ECDSA", server_ecdhe_ecdsa()} - ])). -server_ecdh_rsa() -> - [client_ecdh_rsa_server_ecdh_rsa, - client_ecdhe_rsa_server_ecdh_rsa, - client_ecdhe_ecdsa_server_ecdh_rsa]. - -server_ecdhe_rsa() -> - [client_ecdh_rsa_server_ecdhe_rsa, - client_ecdhe_rsa_server_ecdhe_rsa, - client_ecdhe_ecdsa_server_ecdhe_rsa]. - -server_ecdh_ecdsa() -> - [client_ecdh_ecdsa_server_ecdh_ecdsa, - client_ecdhe_rsa_server_ecdh_ecdsa, - client_ecdhe_ecdsa_server_ecdh_ecdsa]. - -server_ecdhe_ecdsa() -> - [client_ecdh_rsa_server_ecdhe_ecdsa, - client_ecdh_ecdsa_server_ecdhe_ecdsa, - client_ecdhe_ecdsa_server_ecdhe_ecdsa]. - %%-------------------------------------------------------------------- init_per_suite(Config0) -> end_per_suite(Config0), @@ -121,7 +67,8 @@ init_per_suite(Config0) -> end_per_suite(_Config) -> application:stop(ssl), - application:stop(crypto). + application:stop(crypto), + ssl_test_lib:kill_openssl(). %%-------------------------------------------------------------------- init_per_group(GroupName, Config) -> @@ -171,38 +118,6 @@ end_per_testcase(_TestCase, Config) -> skip(Config) when is_list(Config) -> {skip, openssl_does_not_support_ECC}. -%% Test diffrent certificate chain types, note that it is the servers -%% chain that affect what cipher suit that will be choosen - -%% ECDH_RSA -client_ecdh_rsa_server_ecdh_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdh_rsa_server_ecdh_rsa(Config). -client_ecdhe_rsa_server_ecdh_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_rsa_server_ecdh_rsa(Config). -client_ecdhe_ecdsa_server_ecdh_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_ecdsa_server_ecdh_rsa(Config). -%% ECDHE_RSA -client_ecdh_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdh_rsa_server_ecdhe_rsa(Config). -client_ecdhe_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_rsa_server_ecdhe_rsa(Config). -client_ecdhe_ecdsa_server_ecdhe_rsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_ecdsa_server_ecdhe_rsa(Config). -%% ECDH_ECDSA -client_ecdh_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdh_ecdsa_server_ecdh_ecdsa(Config). -client_ecdhe_rsa_server_ecdh_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_rsa_server_ecdh_ecdsa(Config). -client_ecdhe_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_ecdsa_server_ecdh_ecdsa(Config). -%% ECDHE_ECDSA -client_ecdh_rsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdh_rsa_server_ecdhe_ecdsa(Config). -client_ecdh_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdh_ecdsa_server_ecdhe_ecdsa(Config). -client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> - ssl_ECC:client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config). - mix_sign(Config) -> {COpts0, SOpts0} = ssl_test_lib:make_mix_cert(Config), COpts = ssl_test_lib:ssl_options(COpts0, Config), diff --git a/lib/ssl/test/ssl_alert_SUITE.erl b/lib/ssl/test/ssl_alert_SUITE.erl new file mode 100644 index 0000000000..cc0b636580 --- /dev/null +++ b/lib/ssl/test/ssl_alert_SUITE.erl @@ -0,0 +1,100 @@ +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_alert_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-include_lib("ssl/src/ssl_alert.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + alerts, + alert_details, + alert_details_not_too_big + ]. + +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +alerts() -> + [{doc, "Test ssl_alert:alert_txt/1"}]. +alerts(Config) when is_list(Config) -> + Descriptions = [?CLOSE_NOTIFY, ?UNEXPECTED_MESSAGE, ?BAD_RECORD_MAC, + ?DECRYPTION_FAILED_RESERVED, ?RECORD_OVERFLOW, ?DECOMPRESSION_FAILURE, + ?HANDSHAKE_FAILURE, ?BAD_CERTIFICATE, ?UNSUPPORTED_CERTIFICATE, + ?CERTIFICATE_REVOKED,?CERTIFICATE_EXPIRED, ?CERTIFICATE_UNKNOWN, + ?ILLEGAL_PARAMETER, ?UNKNOWN_CA, ?ACCESS_DENIED, ?DECODE_ERROR, + ?DECRYPT_ERROR, ?EXPORT_RESTRICTION, ?PROTOCOL_VERSION, + ?INSUFFICIENT_SECURITY, ?INTERNAL_ERROR, ?USER_CANCELED, + ?NO_RENEGOTIATION, ?UNSUPPORTED_EXTENSION, ?CERTIFICATE_UNOBTAINABLE, + ?UNRECOGNISED_NAME, ?BAD_CERTIFICATE_STATUS_RESPONSE, + ?BAD_CERTIFICATE_HASH_VALUE, ?UNKNOWN_PSK_IDENTITY, + 255 %% Unsupported/unknow alert will result in a description too + ], + Alerts = [?ALERT_REC(?WARNING, ?CLOSE_NOTIFY) | + [?ALERT_REC(?FATAL, Desc) || Desc <- Descriptions]], + lists:foreach(fun(Alert) -> + try ssl_alert:alert_txt(Alert) + catch + C:E:T -> + ct:fail({unexpected, {C, E, T}}) + end + end, Alerts). +%%-------------------------------------------------------------------- +alert_details() -> + [{doc, "Test that ssl_alert:alert_txt/1 result contains extendend error description"}]. +alert_details(Config) when is_list(Config) -> + Unique = make_ref(), + UniqueStr = lists:flatten(io_lib:format("~w", [Unique])), + Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Unique), + case string:str(ssl_alert:alert_txt(Alert), UniqueStr) of + 0 -> + ct:fail(error_details_missing); + _ -> + ok + end. + +%%-------------------------------------------------------------------- +alert_details_not_too_big() -> + [{doc, "Test that ssl_alert:alert_txt/1 limits printed depth of extended error description"}]. +alert_details_not_too_big(Config) when is_list(Config) -> + Reason = lists:duplicate(10, lists:duplicate(10, lists:duplicate(10, {some, data}))), + Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Reason), + case length(ssl_alert:alert_txt(Alert)) < 1000 of + true -> + ok; + false -> + ct:fail(ssl_alert_text_too_big) + end. diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_SUITE.erl index dfc780479e..82a49e1469 100644 --- a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_alpn_SUITE.erl @@ -19,7 +19,7 @@ %% %% --module(ssl_alpn_handshake_SUITE). +-module(ssl_alpn_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). @@ -32,7 +32,9 @@ %%-------------------------------------------------------------------- all() -> - [{group, 'tlsv1.2'}, + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, {group, 'sslv3'}, @@ -42,12 +44,13 @@ all() -> groups() -> [ - {'tlsv1.2', [], alpn_tests()}, - {'tlsv1.1', [], alpn_tests()}, - {'tlsv1', [], alpn_tests()}, + {'tlsv1.3', [], alpn_tests() -- [client_renegotiate, session_reused]}, + {'tlsv1.2', [], alpn_tests() ++ alpn_npn_coexist()}, + {'tlsv1.1', [], alpn_tests() ++ alpn_npn_coexist()}, + {'tlsv1', [], alpn_tests() ++ alpn_npn_coexist()}, {'sslv3', [], alpn_not_supported()}, - {'dtlsv1.2', [], alpn_tests() -- [client_renegotiate]}, - {'dtlsv1', [], alpn_tests() -- [client_renegotiate]} + {'dtlsv1.2', [], alpn_tests() ++ alpn_npn_coexist()}, + {'dtlsv1', [], alpn_tests() ++ alpn_npn_coexist()} ]. alpn_tests() -> @@ -61,12 +64,16 @@ alpn_tests() -> client_alpn_and_server_no_support, client_no_support_and_server_alpn, client_alpn_npn_and_server_alpn, - client_alpn_npn_and_server_alpn_npn, - client_alpn_and_server_alpn_npn, client_renegotiate, session_reused ]. +alpn_npn_coexist() -> + [ + client_alpn_npn_and_server_alpn_npn, + client_alpn_and_server_alpn_npn + ]. + alpn_not_supported() -> [alpn_not_supported_client, alpn_not_supported_server @@ -77,8 +84,7 @@ init_per_suite(Config0) -> try crypto:start() of ok -> ssl_test_lib:clean_start(), - Config = ssl_test_lib:make_rsa_cert(Config0), - ssl_test_lib:cert_options(Config) + ssl_test_lib:make_rsa_cert(Config0) catch _:_ -> {skip, "Crypto did not start"} end. diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl new file mode 100644 index 0000000000..fefecc0b65 --- /dev/null +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -0,0 +1,1976 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(ssl_api_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). + +-define(SLEEP, 500). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [ + {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ handshake_paus_tests()) -- [dh_params, honor_server_cipher_order, honor_client_cipher_order, + new_options_in_handshake]) + ++ (since_1_2() -- [conf_signature_algs])}, + {'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()}, + {'tlsv1.1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3()}, + {'tlsv1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3() ++ beast_mitigation_test()}, + {'sslv3', [], (gen_api_tests() -- [new_options_in_handshake]) ++ beast_mitigation_test() ++ pre_1_3()}, + {'dtlsv1.2', [], (gen_api_tests() -- [invalid_keyfile, invalid_certfile, invalid_cacertfile, + invalid_options, new_options_in_handshake]) ++ handshake_paus_tests() ++ pre_1_3()}, + {'dtlsv1', [], (gen_api_tests() -- [invalid_keyfile, invalid_certfile, invalid_cacertfile, + invalid_options, new_options_in_handshake]) ++ handshake_paus_tests() ++ pre_1_3()} + ]. + +since_1_2() -> + [ + conf_signature_algs, + no_common_signature_algs + ]. + +pre_1_3() -> + [ + default_reject_anonymous + ]. +gen_api_tests() -> + [ + peercert, + peercert_with_client_cert, + connection_information, + secret_connection_info, + versions, + active_n, + dh_params, + hibernate, + hibernate_right_away, + listen_socket, + recv_active, + recv_active_once, + recv_active_n, + recv_timeout, + recv_close, + controlling_process, + controller_dies, + controlling_process_transport_accept_socket, + close_with_timeout, + close_in_error_state, + call_in_error_state, + close_transport_accept, + abuse_transport_accept_socket, + honor_server_cipher_order, + honor_client_cipher_order, + ipv6, + der_input, + new_options_in_handshake, + max_handshake_size, + invalid_certfile, + invalid_cacertfile, + invalid_keyfile, + options_not_proplist, + invalid_options + ]. + +handshake_paus_tests() -> + [ + handshake_continue, + handshake_continue_timeout, + hello_client_cancel, + hello_server_cancel + ]. + +%% Only relevant for SSL 3.0 and TLS 1.1 +beast_mitigation_test() -> + [%% Original option + rizzo_disabled, + %% Same effect as disable + rizzo_zero_n, + %% Same as default + rizzo_one_n_minus_one + ]. + +tls13_group() -> + [ + supported_groups, + honor_server_cipher_order_tls13, + honor_client_cipher_order_tls13 + ]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + [{client_type, erlang}, + {server_type, erlang} | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(prf, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Version = ssl_test_lib:protocol_version(Config), + PRFS = [md5, sha, sha256, sha384, sha512], + %% All are the result of running tls_v1:prf(PrfAlgo, <<>>, <<>>, <<>>, 16) + %% with the specified PRF algorithm + ExpectedPrfResults = + [{md5, <<96,139,180,171,236,210,13,10,28,32,2,23,88,224,235,199>>}, + {sha, <<95,3,183,114,33,169,197,187,231,243,19,242,220,228,70,151>>}, + {sha256, <<166,249,145,171,43,95,158,232,6,60,17,90,183,180,0,155>>}, + {sha384, <<153,182,217,96,186,130,105,85,65,103,123,247,146,91,47,106>>}, + {sha512, <<145,8,98,38,243,96,42,94,163,33,53,49,241,4,127,28>>}, + %% TLS 1.0 and 1.1 PRF: + {md5sha, <<63,136,3,217,205,123,200,177,251,211,17,229,132,4,173,80>>}], + TestPlan = prf_create_plan([Version], PRFS, ExpectedPrfResults), + [{prf_test_plan, TestPlan} | Config]; +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(internal_active_n, _Config) -> + application:unset_env(ssl, internal_active_n); +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +peercert() -> + [{doc,"Test API function peercert/1"}]. +peercert(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, ClientOpts}]), + + CertFile = proplists:get_value(certfile, ServerOpts), + [{'Certificate', BinCert, _}]= ssl_test_lib:pem_to_der(CertFile), + + ServerMsg = {error, no_peercert}, + ClientMsg = {ok, BinCert}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- + +peercert_with_client_cert() -> + [{doc,"Test API function peercert/1"}]. +peercert_with_client_cert(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, [{verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, ClientOpts}]), + + ServerCertFile = proplists:get_value(certfile, ServerOpts), + [{'Certificate', ServerBinCert, _}]= ssl_test_lib:pem_to_der(ServerCertFile), + ClientCertFile = proplists:get_value(certfile, ClientOpts), + [{'Certificate', ClientBinCert, _}]= ssl_test_lib:pem_to_der(ClientCertFile), + + ServerMsg = {ok, ClientBinCert}, + ClientMsg = {ok, ServerBinCert}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +connection_information() -> + [{doc,"Test the API function ssl:connection_information/1"}]. +connection_information(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, connection_information_result, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_information_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- + +secret_connection_info() -> + [{doc,"Test the API function ssl:connection_information/2"}]. +secret_connection_info(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, secret_connection_info_result, []}}, + {options, [{verify, verify_peer} | ServerOpts]}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, secret_connection_info_result, []}}, + {options, [{verify, verify_peer} |ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, true, Client, true), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +prf() -> + [{doc,"Test that ssl:prf/5 uses the negotiated PRF."}]. +prf(Config) when is_list(Config) -> + TestPlan = proplists:get_value(prf_test_plan, Config), + case TestPlan of + [] -> ct:fail({error, empty_prf_test_plan}); + _ -> lists:foreach(fun(Suite) -> + lists:foreach( + fun(Test) -> + V = proplists:get_value(tls_ver, Test), + C = proplists:get_value(ciphers, Test), + E = proplists:get_value(expected, Test), + P = proplists:get_value(prf, Test), + prf_run_test(Config, V, C, E, P) + end, Suite) + end, TestPlan) + end. + +%%-------------------------------------------------------------------- +dh_params() -> + [{doc,"Test to specify DH-params file in server."}]. + +dh_params(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + DataDir = proplists:get_value(data_dir, Config), + DHParamFile = filename:join(DataDir, "dHParam.pem"), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{dhfile, DHParamFile} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, + [{ciphers,[{dhe_rsa,aes_256_cbc,sha}]} | + ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +conf_signature_algs() -> + [{doc,"Test to set the signature_algs option on both client and server"}]. +conf_signature_algs(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +no_common_signature_algs() -> + [{doc,"Set the signature_algs option so that there client and server does not share any hash sign algorithms"}]. +no_common_signature_algs(Config) when is_list(Config) -> + + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [{signature_algs, [{sha256, rsa}]} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, [{signature_algs, [{sha384, rsa}]} + | ClientOpts]}]), + + ssl_test_lib:check_server_alert(Server, Client, insufficient_security). + +%%-------------------------------------------------------------------- +handshake_continue() -> + [{doc, "Test API function ssl:handshake_continue/3"}]. +handshake_continue(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ssl_test_lib:ssl_options([{reuseaddr, true}, + {verify, verify_peer}, + {handshake, hello} | ServerOpts + ], + Config)}, + {continue_options, proplists:delete(reuseaddr, ServerOpts)} + ]), + + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ClientOpts + ], + Config)}, + {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%------------------------------------------------------------------ +handshake_continue_timeout() -> + [{doc, "Test API function ssl:handshake_continue/3 with short timeout"}]. +handshake_continue_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 1}, + {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}, + {verify, verify_peer} | ServerOpts], + Config)}, + {continue_options, proplists:delete(reuseaddr, ServerOpts)} + ]), + + Port = ssl_test_lib:inet_port(Server), + + + ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, [{verify, verify_peer} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, {error,timeout}), + ssl_test_lib:close(Server). + + +%%-------------------------------------------------------------------- +hello_client_cancel() -> + [{doc, "Test API function ssl:handshake_cancel/1 on the client side"}]. +hello_client_cancel(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ServerOpts], Config)}, + {continue_options, proplists:delete(reuseaddr, ServerOpts)}]), + + Port = ssl_test_lib:inet_port(Server), + + %% That is ssl:handshake_cancel returns ok + {connect_failed, ok} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ClientOpts], Config)}, + {continue_options, cancel}]), + ssl_test_lib:check_server_alert(Server, user_canceled). +%%-------------------------------------------------------------------- +hello_server_cancel() -> + [{doc, "Test API function ssl:handshake_cancel/1 on the server side"}]. +hello_server_cancel(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ServerOpts + ], Config)}, + {continue_options, cancel}]), + + Port = ssl_test_lib:inet_port(Server), + + ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ClientOpts + ], Config)}, + {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), + + ssl_test_lib:check_result(Server, ok). + +%%-------------------------------------------------------------------- +versions() -> + [{doc,"Test API function versions/0"}]. + +versions(Config) when is_list(Config) -> + [_|_] = Versions = ssl:versions(), + ct:log("~p~n", [Versions]). + +%%-------------------------------------------------------------------- +%% Test case adapted from gen_tcp_misc_SUITE. +active_n() -> + [{doc,"Test {active,N} option"}]. + +active_n(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Port = ssl_test_lib:inet_port(node()), + N = 3, + LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])), + [{active,N}] = ok(ssl:getopts(LS, [active])), + active_n_common(LS, N), + Self = self(), + spawn_link(fun() -> + S0 = ok(ssl:transport_accept(LS)), + {ok, S} = ssl:handshake(S0), + ok = ssl:setopts(S, [{active,N}]), + [{active,N}] = ok(ssl:getopts(S, [active])), + ssl:controlling_process(S, Self), + Self ! {server, S} + end), + C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])), + [{active,N}] = ok(ssl:getopts(C, [active])), + S = receive + {server, S0} -> S0 + after + 1000 -> + exit({error, connect}) + end, + active_n_common(C, N), + active_n_common(S, N), + ok = ssl:setopts(C, [{active,N}]), + ok = ssl:setopts(S, [{active,N}]), + ReceiveMsg = fun(Socket, Msg) -> + receive + {ssl,Socket,Msg} -> + ok; + {ssl,Socket,Begin} -> + receive + {ssl,Socket,End} -> + Msg = Begin ++ End, + ok + after 1000 -> + exit(timeout) + end + after 1000 -> + exit(timeout) + end + end, + repeat(3, fun(I) -> + Msg = "message "++integer_to_list(I), + ok = ssl:send(C, Msg), + ReceiveMsg(S, Msg), + ok = ssl:send(S, Msg), + ReceiveMsg(C, Msg) + end), + receive + {ssl_passive,S} -> + [{active,false}] = ok(ssl:getopts(S, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + receive + {ssl_passive,C} -> + [{active,false}] = ok(ssl:getopts(C, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + LS2 = ok(ssl:listen(0, [{active,0}])), + receive + {ssl_passive,LS2} -> + [{active,false}] = ok(ssl:getopts(LS2, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + ok = ssl:close(LS2), + ok = ssl:close(C), + ok = ssl:close(S), + ok = ssl:close(LS), + ok. + +hibernate() -> + [{doc,"Check that an SSL connection that is started with option " + "{hibernate_after, 1000} indeed hibernates after 1000ms of " + "inactivity"}]. + +hibernate(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, #sslsocket{pid=[Pid|_]}} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{hibernate_after, 1000}|ClientOpts]}]), + {current_function, _} = + process_info(Pid, current_function), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ct:sleep(1500), + {current_function, {erlang, hibernate, 3}} = + process_info(Pid, current_function), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- + +hibernate_right_away() -> + [{doc,"Check that an SSL connection that is configured to hibernate " + "after 0 or 1 milliseconds hibernates as soon as possible and not " + "crashes"}]. + +hibernate_right_away(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + StartServerOpts = [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}], + StartClientOpts = [return_socket, + {node, ClientNode}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}], + + Server1 = ssl_test_lib:start_server(StartServerOpts), + Port1 = ssl_test_lib:inet_port(Server1), + {Client1, #sslsocket{pid = [Pid1|_]}} = ssl_test_lib:start_client(StartClientOpts ++ + [{port, Port1}, {options, [{hibernate_after, 0}|ClientOpts]}]), + + ssl_test_lib:check_result(Server1, ok, Client1, ok), + + ct:sleep(1000), %% Schedule out + + {current_function, {erlang, hibernate, 3}} = + process_info(Pid1, current_function), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1), + + Server2 = ssl_test_lib:start_server(StartServerOpts), + Port2 = ssl_test_lib:inet_port(Server2), + {Client2, #sslsocket{pid = [Pid2|_]}} = ssl_test_lib:start_client(StartClientOpts ++ + [{port, Port2}, {options, [{hibernate_after, 1}|ClientOpts]}]), + + ssl_test_lib:check_result(Server2, ok, Client2, ok), + + ct:sleep(1000), %% Schedule out + + {current_function, {erlang, hibernate, 3}} = + process_info(Pid2, current_function), + + ssl_test_lib:close(Server2), + ssl_test_lib:close(Client2). + +listen_socket() -> + [{doc,"Check error handling and inet compliance when calling API functions with listen sockets."}]. + +listen_socket(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ok, ListenSocket} = ssl:listen(0, ServerOpts), + + %% This can be a valid thing to do as + %% options are inherited by the accept socket + ok = ssl:controlling_process(ListenSocket, self()), + + {ok, _} = ssl:sockname(ListenSocket), + + {error, enotconn} = ssl:send(ListenSocket, <<"data">>), + {error, enotconn} = ssl:recv(ListenSocket, 0), + {error, enotconn} = ssl:connection_information(ListenSocket), + {error, enotconn} = ssl:peername(ListenSocket), + {error, enotconn} = ssl:peercert(ListenSocket), + {error, enotconn} = ssl:renegotiate(ListenSocket), + {error, enotconn} = ssl:prf(ListenSocket, 'master_secret', <<"Label">>, [client_random], 256), + {error, enotconn} = ssl:shutdown(ListenSocket, read_write), + + ok = ssl:close(ListenSocket). + +%%-------------------------------------------------------------------- +recv_active() -> + [{doc,"Test recv on active socket"}]. + +recv_active(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active, []}}, + {options, [{active, true} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active, []}}, + {options, [{active, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +recv_active_once() -> + [{doc,"Test recv on active (once) socket"}]. + +recv_active_once(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, once} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, once} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +recv_active_n() -> + [{doc,"Test recv on active (n) socket"}]. + +recv_active_n(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, 1} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, 1} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +recv_timeout() -> + [{doc,"Test ssl:ssl_accept timeout"}]. + +recv_timeout(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_timeout_server, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + send_recv_result_timeout_client, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +recv_close() -> + [{doc,"Special case of call error handling"}]. +recv_close(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, do_recv_close, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + {_Client, #sslsocket{} = SslSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + ssl:close(SslSocket), + ssl_test_lib:check_result(Server, ok). + + + +%%-------------------------------------------------------------------- +controlling_process() -> + [{doc,"Test API function controlling_process/2"}]. + +controlling_process(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientMsg = "Server hello", + ServerMsg = "Client hello", + + Server = ssl_test_lib:start_server([ + {node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + controlling_process_result, [self(), + ServerMsg]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, CSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + controlling_process_result, [self(), + ClientMsg]}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ServerMsg = ssl_test_lib:active_recv(CSocket, length(ServerMsg)), + %% We do not have the TLS server socket but all messages form the client + %% socket are now read, so ramining are form the server socket + ClientMsg = ssl_active_recv(length(ClientMsg)), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +controller_dies() -> + [{doc,"Test that the socket is closed after controlling process dies"}]. +controller_dies(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientMsg = "Hello server", + ServerMsg = "Hello client", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + controller_dies_result, [self(), + ServerMsg]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + controller_dies_result, [self(), + ClientMsg]}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), + ct:sleep(?SLEEP), %% so that they are connected + + process_flag(trap_exit, true), + + %% Test that clients die + exit(Client, killed), + get_close(Client, ?LINE), + + %% Test that clients die when process disappear + Server ! listen, + Tester = self(), + Connect = fun(Pid) -> + {ok, Socket} = ssl:connect(Hostname, Port, ClientOpts), + %% Make sure server finishes and verification + %% and is in coonection state before + %% killing client + ct:sleep(?SLEEP), + Pid ! {self(), connected, Socket}, + receive die_nice -> normal end + end, + Client2 = spawn_link(fun() -> Connect(Tester) end), + receive {Client2, connected, _Socket} -> Client2 ! die_nice end, + + get_close(Client2, ?LINE), + + %% Test that clients die when the controlling process have changed + Server ! listen, + + Client3 = spawn_link(fun() -> Connect(Tester) end), + Controller = spawn_link(fun() -> receive die_nice -> normal end end), + receive + {Client3, connected, Socket} -> + ok = ssl:controlling_process(Socket, Controller), + Client3 ! die_nice + end, + + ct:log("Wating on exit ~p~n",[Client3]), + receive {'EXIT', Client3, normal} -> ok end, + + receive %% Client3 is dead but that doesn't matter, socket should not be closed. + Unexpected -> + ct:log("Unexpected ~p~n",[Unexpected]), + ct:fail({line, ?LINE-1}) + after 1000 -> + ok + end, + Controller ! die_nice, + get_close(Controller, ?LINE), + + %% Test that servers die + Server ! listen, + LastClient = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + controller_dies_result, [self(), + ClientMsg]}}, + {options, ClientOpts}]), + ct:sleep(?SLEEP), %% so that they are connected + + exit(Server, killed), + get_close(Server, ?LINE), + process_flag(trap_exit, false), + ssl_test_lib:close(LastClient). +%%-------------------------------------------------------------------- +controlling_process_transport_accept_socket() -> + [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. +controlling_process_transport_accept_socket(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_transport_control([{node, ServerNode}, + {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + _Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server). + +%%-------------------------------------------------------------------- +close_with_timeout() -> + [{doc,"Test normal (not downgrade) ssl:close/2"}]. +close_with_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_close, []}}, + {options,[{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_close, []}}, + {options, [{active, false} |ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + +%%-------------------------------------------------------------------- +close_in_error_state() -> + [{doc,"Special case of closing socket in error state"}]. +close_in_error_state(Config) when is_list(Config) -> + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + _ = spawn_link(?MODULE, run_error_server_close, [[self() | ServerOpts]]), + receive + {_Pid, Port} -> + spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) + end, + receive + ok -> + ok; + Other -> + ct:fail(Other) + end. + +%%-------------------------------------------------------------------- +call_in_error_state() -> + [{doc,"Special case of call error handling"}]. +call_in_error_state(Config) when is_list(Config) -> + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], + Pid = spawn_link(?MODULE, run_error_server, [[self() | ServerOpts]]), + receive + {Pid, Port} -> + spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) + end, + receive + {error, closed} -> + ok; + Other -> + ct:fail(Other) + end. +%%-------------------------------------------------------------------- +close_transport_accept() -> + [{doc,"Tests closing ssl socket when waiting on ssl:transport_accept/1"}]. + +close_transport_accept(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), + + Port = 0, + Opts = [{active, false} | ServerOpts], + {ok, ListenSocket} = rpc:call(ServerNode, ssl, listen, [Port, Opts]), + spawn_link(fun() -> + ct:sleep(?SLEEP), + rpc:call(ServerNode, ssl, close, [ListenSocket]) + end), + case rpc:call(ServerNode, ssl, transport_accept, [ListenSocket]) of + {error, closed} -> + ok; + Other -> + exit({?LINE, Other}) + end. +%%-------------------------------------------------------------------- +abuse_transport_accept_socket() -> + [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. +abuse_transport_accept_socket(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_transport_abuse_socket([{node, ServerNode}, + {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- + +invalid_keyfile() -> + [{doc,"Test what happens with an invalid key file"}]. +invalid_keyfile(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + BadKeyFile = filename:join([proplists:get_value(priv_dir, Config), + "badkey.pem"]), + BadOpts = [{keyfile, BadKeyFile}| proplists:delete(keyfile, ServerOpts)], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, BadOpts}]), + + Port = ssl_test_lib:inet_port(Server), + + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {from, self()}, {options, ClientOpts}]), + + File = proplists:get_value(keyfile,BadOpts), + ssl_test_lib:check_result(Server, {error,{options, {keyfile, File, {error,enoent}}}}, Client, + {error, closed}). + +%%-------------------------------------------------------------------- +honor_server_cipher_order() -> + [{doc,"Test API honor server cipher order."}]. +honor_server_cipher_order(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}], + ServerCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac =>sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}], + honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}). + +%%-------------------------------------------------------------------- +honor_client_cipher_order() -> + [{doc,"Test API honor server cipher order."}]. +honor_client_cipher_order(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}], + ServerCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac =>sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}], + honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}). + +%%-------------------------------------------------------------------- +ipv6() -> + [{require, ipv6_hosts}, + {doc,"Test ipv6."}]. +ipv6(Config) when is_list(Config) -> + {ok, Hostname0} = inet:gethostname(), + + case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of + true -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = + ssl_test_lib:run_where(Config, ipv6), + Server = ssl_test_lib:start_server([{node, ServerNode}, + {port, 0}, {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, + [inet6, {active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, + [inet6, {active, false} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client); + false -> + {skip, "Host does not support IPv6"} + end. + +%%-------------------------------------------------------------------- +der_input() -> + [{doc,"Test to input certs and key as der"}]. + +der_input(Config) when is_list(Config) -> + DataDir = proplists:get_value(data_dir, Config), + DHParamFile = filename:join(DataDir, "dHParam.pem"), + + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + [CADb | _] = element(6, State), + + Size = ets:info(CADb, size), + + SeverVerifyOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ServerCert, ServerKey, ServerCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | + SeverVerifyOpts]), + ClientVerifyOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + {ClientCert, ClientKey, ClientCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | + ClientVerifyOpts]), + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, + {dh, DHParams}, + {cert, ServerCert}, {key, ServerKey}, {cacerts, ServerCaCerts}], + ClientOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, + {dh, DHParams}, + {cert, ClientCert}, {key, ClientKey}, {cacerts, ClientCaCerts}], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + Size = ets:info(CADb, size). + +%%-------------------------------------------------------------------- +invalid_certfile() -> + [{doc,"Test what happens with an invalid cert file"}]. + +invalid_certfile(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + BadCertFile = filename:join([proplists:get_value(priv_dir, Config), + "badcert.pem"]), + ServerBadOpts = [{certfile, BadCertFile}| proplists:delete(certfile, ServerOpts)], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerBadOpts}]), + + Port = ssl_test_lib:inet_port(Server), + + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + File = proplists:get_value(certfile, ServerBadOpts), + ssl_test_lib:check_result(Server, {error,{options, {certfile, File, {error,enoent}}}}, + Client, {error, closed}). + + +%%-------------------------------------------------------------------- +invalid_cacertfile() -> + [{doc,"Test what happens with an invalid cacert file"}]. + +invalid_cacertfile(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + BadCACertFile = filename:join([proplists:get_value(priv_dir, Config), + "badcacert.pem"]), + ServerBadOpts = [{cacertfile, BadCACertFile}| proplists:delete(cacertfile, ServerOpts)], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server0 = + ssl_test_lib:start_server_error([{node, ServerNode}, + {port, 0}, {from, self()}, + {options, ServerBadOpts}]), + + Port0 = ssl_test_lib:inet_port(Server0), + + + Client0 = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + + File0 = proplists:get_value(cacertfile, ServerBadOpts), + + ssl_test_lib:check_result(Server0, {error, {options, {cacertfile, File0,{error,enoent}}}}, + Client0, {error, closed}), + + File = File0 ++ "do_not_exit.pem", + ServerBadOpts1 = [{cacertfile, File}|proplists:delete(cacertfile, ServerBadOpts)], + + Server1 = + ssl_test_lib:start_server_error([{node, ServerNode}, + {port, 0}, {from, self()}, + {options, ServerBadOpts1}]), + + Port1 = ssl_test_lib:inet_port(Server1), + + Client1 = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port1}, {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + + + ssl_test_lib:check_result(Server1, {error, {options, {cacertfile, File,{error,enoent}}}}, + Client1, {error, closed}), + ok. + +%%-------------------------------------------------------------------- +new_options_in_handshake() -> + [{doc,"Test that you can set ssl options in handshake/3 and not only in tcp upgrade"}]. +new_options_in_handshake(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Version = ssl_test_lib:protocol_version(Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + [_, Cipher | _] = ssl:filter_cipher_suites(ssl:cipher_suites(all, Version), + [{key_exchange, + fun(dhe_rsa) -> + true; + (ecdhe_rsa) -> + true; + (ecdh_rsa) -> + true; + (rsa) -> + true; + (_) -> + false + end + }]), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {ssl_extra_opts, [{versions, [Version]}, + {ciphers,[Cipher]}]}, %% To be set in ssl_accept/3 + {mfa, {?MODULE, connection_info_result, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{ciphers, [Cipher]} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ServerMsg = ClientMsg = {ok, {Version, Cipher}}, + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%------------------------------------------------------------------- +max_handshake_size() -> + [{doc,"Test that we can set max_handshake_size to max value."}]. + +max_handshake_size(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{max_handshake_size, 8388607} |ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{max_handshake_size, 8388607} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + + +%%------------------------------------------------------------------- +options_not_proplist() -> + [{doc,"Test what happens if an option is not a key value tuple"}]. + +options_not_proplist(Config) when is_list(Config) -> + BadOption = {client_preferred_next_protocols, + client, [<<"spdy/3">>,<<"http/1.1">>], <<"http/1.1">>}, + {option_not_a_key_value_tuple, BadOption} = + ssl:connect("twitter.com", 443, [binary, {active, false}, + BadOption]). + +%%------------------------------------------------------------------- +invalid_options() -> + [{doc,"Test what happens when we give invalid options"}]. + +invalid_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) -> + ssl_test_lib:check_result(Server, + {error, {options, {sslv2, Option}}}, + Client, + {error, {options, {sslv2, Option}}}); + (Client, Server, Option) -> + ssl_test_lib:check_result(Server, + {error, {options, Option}}, + Client, + {error, {options, Option}}) + end, + + TestOpts = + [{versions, [sslv2, sslv3]}, + {verify, 4}, + {verify_fun, function}, + {fail_if_no_peer_cert, 0}, + {verify_client_once, 1}, + {depth, four}, + {certfile, 'cert.pem'}, + {keyfile,'key.pem' }, + {password, foo}, + {cacertfile, ""}, + {dhfile,'dh.pem' }, + {ciphers, [{foo, bar, sha, ignore}]}, + {reuse_session, foo}, + {reuse_sessions, 0}, + {renegotiate_at, "10"}, + {mode, depech}, + {packet, 8.0}, + {packet_size, "2"}, + {header, a}, + {active, trice}, + {key, 'key.pem' }], + + [begin + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [TestOpt | ServerOpts]}]), + %% Will never reach a point where port is used. + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, + {host, Hostname}, {from, self()}, + {options, [TestOpt | ClientOpts]}]), + Check(Client, Server, TestOpt), + ok + end || TestOpt <- TestOpts], + ok. +%%------------------------------------------------------------------- + +default_reject_anonymous()-> + [{doc,"Test that by default anonymous cipher suites are rejected "}]. +default_reject_anonymous(Config) when is_list(Config) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Version = ssl_test_lib:protocol_version(Config), + TLSVersion = ssl_test_lib:tls_version(Version), + + [CipherSuite | _] = ssl_test_lib:ecdh_dh_anonymous_suites(TLSVersion), + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, + [{ciphers,[CipherSuite]} | + ClientOpts]}]), + + ssl_test_lib:check_server_alert(Server, Client, insufficient_security). + +%%------------------------------------------------------------------- +%% Note that these test only test that the options are valid to set. As application data +%% is a stream you can not test that the send acctually splits it up as when it arrives +%% again at the user layer it may be concatenated. But COVER can show that the split up +%% code has been run. + +rizzo_disabled() -> + [{doc, "Test original beast mitigation disable option for SSL 3.0 and TLS 1.0"}]. + +rizzo_disabled(Config) -> + ClientOpts = [{beast_mitigation, disabled} | ssl_test_lib:ssl_options(client_rsa_opts, Config)], + ServerOpts = [{beast_mitigation, disabled} | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%------------------------------------------------------------------- +rizzo_zero_n() -> + [{doc, "Test zero_n beast mitigation option (same affect as original disable option) for SSL 3.0 and TLS 1.0"}]. + +rizzo_zero_n(Config) -> + ClientOpts = [{beast_mitigation, zero_n} | ssl_test_lib:ssl_options(client_rsa_opts, Config)], + ServerOpts = [{beast_mitigation, zero_n} | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%------------------------------------------------------------------- +rizzo_one_n_minus_one () -> + [{doc, "Test beast_mitigation option one_n_minus_one (same affect as default) for SSL 3.0 and TLS 1.0"}]. + +rizzo_one_n_minus_one (Config) -> + ClientOpts = [{beast_mitigation, one_n_minus_one } | ssl_test_lib:ssl_options(client_rsa_opts, Config)], + ServerOpts = [{beast_mitigation, one_n_minus_one} | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +supported_groups() -> + [{doc,"Test the supported_groups option in TLS 1.3."}]. + +supported_groups(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{supported_groups, [x448, x25519]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{supported_groups,[x448]} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +honor_client_cipher_order_tls13() -> + [{doc,"Test API honor server cipher order in TLS 1.3."}]. +honor_client_cipher_order_tls13(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}, + #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}], + ServerCiphers = [#{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}, + #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}], + honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}). + +%%-------------------------------------------------------------------- +honor_server_cipher_order_tls13() -> + [{doc,"Test API honor server cipher order in TLS 1.3."}]. +honor_server_cipher_order_tls13(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}, + #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}], + ServerCiphers = [#{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}, + #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}], + honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +peercert_result(Socket) -> + ssl:peercert(Socket). + +connection_information_result(Socket) -> + {ok, Info = [_ | _]} = ssl:connection_information(Socket), + case length(Info) > 3 of + true -> + %% Atleast one ssl_option() is set + ct:log("Info ~p", [Info]), + ok; + false -> + ct:fail(no_ssl_options_returned) + end. +secret_connection_info_result(Socket) -> + {ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]), + {ok, ConnInfo} = ssl:connection_information(Socket, [client_random, server_random, master_secret]), + check_connection_info(Protocol, ConnInfo). + + +%% In TLS 1.3 the master_secret field is used to store multiple secrets from the key schedule and it is a tuple. +%% client_random and server_random are not used in the TLS 1.3 key schedule. +check_connection_info('tlsv1.3', [{client_random, ClientRand}, {master_secret, {master_secret, MasterSecret}}]) -> + is_binary(ClientRand) andalso is_binary(MasterSecret); +check_connection_info('tlsv1.3', [{server_random, ServerRand}, {master_secret, {master_secret, MasterSecret}}]) -> + is_binary(ServerRand) andalso is_binary(MasterSecret); +check_connection_info(_, [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]) -> + is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret); +check_connection_info(_, _) -> + false. + + +prf_create_plan(TlsVersions, PRFs, Results) -> + lists:foldl(fun(Ver, Acc) -> + A = prf_ciphers_and_expected(Ver, PRFs, Results), + [A|Acc] + end, [], TlsVersions). + +prf_ciphers_and_expected(TlsVer, PRFs, Results) -> + case TlsVer of + TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1 + orelse TlsVer == 'tlsv1.1' orelse TlsVer == 'dtlsv1' -> + Ciphers = ssl:cipher_suites(), + {_, Expected} = lists:keyfind(md5sha, 1, Results), + [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]]; + TlsVer when TlsVer == 'tlsv1.2' orelse TlsVer == 'dtlsv1.2'-> + lists:foldl( + fun(PRF, Acc) -> + Ciphers = prf_get_ciphers(TlsVer, PRF), + case Ciphers of + [] -> + ct:log("No ciphers for PRF algorithm ~p. Skipping.", [PRF]), + Acc; + Ciphers -> + {_, Expected} = lists:keyfind(PRF, 1, Results), + [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, + {prf, PRF}] | Acc] + end + end, [], PRFs) + end. + +prf_get_ciphers(_, PRF) -> + lists:filter( + fun(C) when tuple_size(C) == 4 andalso + element(4, C) == PRF -> + true; + (_) -> + false + end, + ssl:cipher_suites()). + +prf_run_test(_, TlsVer, [], _, Prf) -> + ct:fail({error, cipher_list_empty, TlsVer, Prf}); +prf_run_test(Config, TlsVer, Ciphers, Expected, Prf) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + BaseOpts = [{active, true}, {versions, [TlsVer]}, {ciphers, Ciphers}, {protocol, tls_or_dtls(TlsVer)}], + ServerOpts = BaseOpts ++ proplists:get_value(server_opts, Config), + ClientOpts = BaseOpts ++ proplists:get_value(client_opts, Config), + Server = ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, {from, self()}, + {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, {from, self()}, + {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, + {options, ClientOpts}]), + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +prf_verify_value(Socket, TlsVer, Expected, Algo) -> + Ret = ssl:prf(Socket, <<>>, <<>>, [<<>>], 16), + case TlsVer of + sslv3 -> + case Ret of + {error, undefined} -> ok; + _ -> + {error, {expected, {error, undefined}, + got, Ret, tls_ver, TlsVer, prf_algorithm, Algo}} + end; + _ -> + case Ret of + {ok, Expected} -> ok; + {ok, Val} -> {error, {expected, Expected, got, Val, tls_ver, TlsVer, + prf_algorithm, Algo}} + end + end. + +tls_or_dtls('dtlsv1') -> + dtls; +tls_or_dtls('dtlsv1.2') -> + dtls; +tls_or_dtls(_) -> + tls. + +active_n_common(S, N) -> + ok = ssl:setopts(S, [{active,-N}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + [{active,false}] = ok(ssl:getopts(S, [active])), + ok = ssl:setopts(S, [{active,0}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + ok = ssl:setopts(S, [{active,32767}]), + {error,{options,_}} = ssl:setopts(S, [{active,1}]), + {error,{options,_}} = ssl:setopts(S, [{active,-32769}]), + ok = ssl:setopts(S, [{active,-32768}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + [{active,false}] = ok(ssl:getopts(S, [active])), + ok = ssl:setopts(S, [{active,N}]), + ok = ssl:setopts(S, [{active,true}]), + [{active,true}] = ok(ssl:getopts(S, [active])), + receive + _ -> error({error,active_n}) + after + 0 -> + ok + end, + ok = ssl:setopts(S, [{active,N}]), + ok = ssl:setopts(S, [{active,once}]), + [{active,once}] = ok(ssl:getopts(S, [active])), + receive + _ -> error({error,active_n}) + after + 0 -> + ok + end, + {error,{options,_}} = ssl:setopts(S, [{active,32768}]), + ok = ssl:setopts(S, [{active,false}]), + [{active,false}] = ok(ssl:getopts(S, [active])), + ok. + +ok({ok,V}) -> V. + +repeat(N, Fun) -> + repeat(N, N, Fun). + +repeat(N, T, Fun) when is_integer(N), N > 0 -> + Fun(T-N), + repeat(N-1, T, Fun); +repeat(_, _, _) -> + ok. + +try_recv_active(Socket) -> + ssl:send(Socket, "Hello world"), + {error, einval} = ssl:recv(Socket, 11), + ok. +try_recv_active_once(Socket) -> + {error, einval} = ssl:recv(Socket, 11), + ok. + +controlling_process_result(Socket, Pid, Msg) -> + ok = ssl:controlling_process(Socket, Pid), + %% Make sure other side has evaluated controlling_process + %% before message is sent + ct:sleep(?SLEEP), + ssl:send(Socket, Msg), + no_result_msg. + +controller_dies_result(_Socket, _Pid, _Msg) -> + receive Result -> Result end. +get_close(Pid, Where) -> + receive + {'EXIT', Pid, _Reason} -> + receive + {_, {ssl_closed, Socket}} -> + ct:log("Socket closed ~p~n",[Socket]); + Unexpected -> + ct:log("Unexpected ~p~n",[Unexpected]), + ct:fail({line, ?LINE-1}) + after 5000 -> + ct:fail({timeout, {line, ?LINE, Where}}) + end; + Unexpected -> + ct:log("Unexpected ~p~n",[Unexpected]), + ct:fail({line, ?LINE-1}) + after 5000 -> + ct:fail({timeout, {line, ?LINE, Where}}) + end. + +ssl_active_recv(N) -> + ssl_active_recv(N, []). + +ssl_active_recv(0, Acc) -> + Acc; +ssl_active_recv(N, Acc) -> + receive + {ssl, _, Bytes} -> + ssl_active_recv(N-length(Bytes), Acc ++ Bytes) + end. + +send_recv_result_timeout_client(Socket) -> + {error, timeout} = ssl:recv(Socket, 11, 500), + {error, timeout} = ssl:recv(Socket, 11, 0), + ssl:send(Socket, "Hello world"), + receive + Msg -> + io:format("Msg ~p~n",[Msg]) + after 500 -> + ok + end, + {ok, "Hello world"} = ssl:recv(Socket, 11, 500), + ok. +send_recv_result_timeout_server(Socket) -> + ssl:send(Socket, "Hello"), + {ok, "Hello world"} = ssl:recv(Socket, 11), + ssl:send(Socket, " world"), + ok. + +do_recv_close(Socket) -> + {error, closed} = ssl:recv(Socket, 11), + receive + {_,{error,closed}} -> + error_extra_close_sent_to_user_process + after 500 -> + ok + end. + +tls_close(Socket) -> + ok = ssl_test_lib:send_recv_result(Socket), + case ssl:close(Socket, 5000) of + ok -> + ok; + {error, closed} -> + ok; + Other -> + ct:fail(Other) + end. + +run_error_server_close([Pid | Opts]) -> + {ok, Listen} = ssl:listen(0, Opts), + {ok,{_, Port}} = ssl:sockname(Listen), + Pid ! {self(), Port}, + {ok, Socket} = ssl:transport_accept(Listen), + Pid ! ssl:close(Socket). + +run_error_server([ Pid | Opts]) -> + {ok, Listen} = ssl:listen(0, Opts), + {ok,{_, Port}} = ssl:sockname(Listen), + Pid ! {self(), Port}, + {ok, Socket} = ssl:transport_accept(Listen), + Pid ! ssl:controlling_process(Socket, self()). + +run_client_error([Port, Opts]) -> + ssl:connect("localhost", Port, Opts). + +honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{ciphers, ServerCiphers}, {honor_cipher_order, Honor} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{ciphers, ClientCiphers}, {honor_cipher_order, Honor} + | ClientOpts]}]), + + Version = ssl_test_lib:protocol_version(Config), + + ServerMsg = ClientMsg = {ok, {Version, Expected}}, + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +connection_info_result(Socket) -> + {ok, Info} = ssl:connection_information(Socket, [protocol, selected_cipher_suite]), + {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_cipher_suite, Info)}}. + +der_input_opts(Opts) -> + Certfile = proplists:get_value(certfile, Opts), + CaCertsfile = proplists:get_value(cacertfile, Opts), + Keyfile = proplists:get_value(keyfile, Opts), + Dhfile = proplists:get_value(dhfile, Opts), + [{_, Cert, _}] = ssl_test_lib:pem_to_der(Certfile), + [{Asn1Type, Key, _}] = ssl_test_lib:pem_to_der(Keyfile), + [{_, DHParams, _}] = ssl_test_lib:pem_to_der(Dhfile), + CaCerts = + lists:map(fun(Entry) -> + {_, CaCert, _} = Entry, + CaCert + end, ssl_test_lib:pem_to_der(CaCertsfile)), + {Cert, {Asn1Type, Key}, CaCerts, DHParams}. diff --git a/lib/ssl/test/ssl_basic_SUITE_data/dHParam.pem b/lib/ssl/test/ssl_api_SUITE_data/dHParam.pem index feb581da30..feb581da30 100644 --- a/lib/ssl/test/ssl_basic_SUITE_data/dHParam.pem +++ b/lib/ssl/test/ssl_api_SUITE_data/dHParam.pem diff --git a/lib/ssl/test/ssl_app_env_SUITE.erl b/lib/ssl/test/ssl_app_env_SUITE.erl new file mode 100644 index 0000000000..27fbcb8e47 --- /dev/null +++ b/lib/ssl/test/ssl_app_env_SUITE.erl @@ -0,0 +1,171 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(ssl_app_env_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). + +-define(SLEEP, 500). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [ + {'tlsv1.3', [], tests()}, + {'tlsv1.2', [], tests()}, + {'tlsv1.1', [], tests()}, + {'tlsv1', [], tests()}, + {'sslv3', [], tests()}, + {'dtlsv1.2', [], tests()}, + {'dtlsv1', [], tests()} + ]. + +tests() -> + [ + internal_active_1, + protocol_versions, + empty_protocol_versions + ]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + [{client_type, erlang}, + {server_type, erlang} | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(internal_active_1, Config) -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, internal_active_n, 1), + ssl:start(), + ct:timetrap({seconds, 5}), + Config; +init_per_testcase(protocol_versions, Config) -> + Version = ssl_test_lib:protocol_version(Config), + case atom_to_list(Version) of + "d" ++ _ -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, dtls_protocol_version, [Version]), + ssl:start(); + _ -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, protocol_version, [Version]), + ssl:start() + end, + ct:timetrap({seconds, 5}), + Config; +init_per_testcase(empty_protocol_versions, Config) -> + ssl:stop(), + application:load(ssl), + ssl_test_lib:clean_env(), + application:set_env(ssl, protocol_version, []), + application:set_env(ssl, dtls_protocol_version, []), + ssl:start(), + ct:timetrap({seconds, 5}), + Config; +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_, _Config) -> + ssl_test_lib:clean_start(). + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- +internal_active_1() -> + [{doc,"Test internal active 1 (behave as internal active once)"}]. + +internal_active_1(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +protocol_versions() -> + [{doc,"Test to set a list of protocol versions in app environment."}]. + +protocol_versions(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +empty_protocol_versions() -> + [{doc,"Test to set an empty list of protocol versions in app environment."}]. + +empty_protocol_versions(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 4452adaea7..355cd31070 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -26,14 +26,8 @@ -compile(export_all). -include_lib("common_test/include/ct.hrl"). --include_lib("public_key/include/public_key.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). --include("ssl_api.hrl"). --include("ssl_internal.hrl"). --include("ssl_alert.hrl"). --include("ssl_internal.hrl"). --include("tls_record.hrl"). --include("tls_handshake.hrl"). -define(TIMEOUT, 20000). -define(EXPIRE, 10). @@ -48,228 +42,35 @@ all() -> [ {group, basic}, - {group, basic_tls}, - {group, options}, - {group, options_tls}, - {group, session}, - {group, 'dtlsv1.2'}, - {group, 'dtlsv1'}, - {group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'sslv3'} + {group, options} ]. groups() -> [{basic, [], basic_tests()}, - {basic_tls, [], basic_tests_tls()}, - {options, [], options_tests()}, - {options_tls, [], options_tests_tls()}, - {'dtlsv1.2', [], all_versions_groups()}, - {'dtlsv1', [], all_versions_groups()}, - {'tlsv1.2', [], all_versions_groups() ++ tls_versions_groups() ++ [conf_signature_algs, no_common_signature_algs]}, - {'tlsv1.1', [], all_versions_groups() ++ tls_versions_groups()}, - {'tlsv1', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests()}, - {'sslv3', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests() ++ [tls_ciphersuite_vs_version]}, - {api,[], api_tests()}, - {api_tls,[], api_tests_tls()}, - {tls_ciphers,[], tls_cipher_tests()}, - {session, [], session_tests()}, - {renegotiate, [], renegotiate_tests()}, - {ciphers, [], cipher_tests()}, - {ciphers_ec, [], cipher_tests_ec()}, - {error_handling_tests, [], error_handling_tests()}, - {error_handling_tests_tls, [], error_handling_tests_tls()} + {options, [], options_tests()} ]. -tls_versions_groups ()-> - [ - {group, api_tls}, - {group, tls_ciphers}, - {group, error_handling_tests_tls}]. - -all_versions_groups ()-> - [{group, api}, - {group, renegotiate}, - {group, ciphers}, - {group, ciphers_ec}, - {group, error_handling_tests}]. - - basic_tests() -> [app, - appup, - alerts, - alert_details, - alert_details_not_too_big, + appup, version_option, connect_twice, connect_dist, - clear_pem_cache, defaults, fallback, cipher_format, - suite_to_str - ]. - -basic_tests_tls() -> - [tls_send_close - ]. - -options_tests() -> - [der_input, - ssl_options_not_proplist, - raw_ssl_option, - invalid_inet_get_option, - invalid_inet_get_option_not_list, - invalid_inet_get_option_improper_list, - invalid_inet_set_option, - invalid_inet_set_option_not_list, - invalid_inet_set_option_improper_list, - dh_params, - invalid_certfile, - invalid_cacertfile, - invalid_keyfile, - invalid_options, - protocol_versions, - empty_protocol_versions, - ipv6, - reuseaddr, - honor_server_cipher_order, - honor_client_cipher_order, - unordered_protocol_versions_server, - unordered_protocol_versions_client, - max_handshake_size -]. - -options_tests_tls() -> - [tls_misc_ssl_options, - tls_tcp_reuseaddr]. - -api_tests() -> - [secret_connection_info, - connection_information, - peercert, - peercert_with_client_cert, - versions, + tls_versions_option, eccs, - controlling_process, - getstat, - close_with_timeout, - hibernate, - hibernate_right_away, - listen_socket, - ssl_recv_timeout, - server_name_indication_option, - accept_pool, - prf, - socket_options, - active_n, cipher_suites, - handshake_continue, - handshake_continue_timeout, - hello_client_cancel, - hello_server_cancel - ]. - -api_tests_tls() -> - [tls_versions_option, - tls_upgrade, - tls_upgrade_with_timeout, - tls_ssl_accept_timeout, - tls_downgrade, - tls_shutdown, - tls_shutdown_write, - tls_shutdown_both, - tls_shutdown_error, - peername, - sockname, - tls_socket_options, - new_options_in_accept - ]. - -session_tests() -> - [reuse_session, - reuse_session_expired, - server_does_not_want_to_reuse_session, - no_reuses_session_server_restart_new_cert, - no_reuses_session_server_restart_new_cert_file]. - -renegotiate_tests() -> - [client_renegotiate, - server_renegotiate, - client_secure_renegotiate, - client_secure_renegotiate_fallback, - client_renegotiate_reused_session, - server_renegotiate_reused_session, - client_no_wrap_sequence_number, - server_no_wrap_sequence_number, - renegotiate_dos_mitigate_active, - renegotiate_dos_mitigate_passive, - renegotiate_dos_mitigate_absolute]. - -tls_cipher_tests() -> - [rc4_rsa_cipher_suites, - rc4_ecdh_rsa_cipher_suites, - rc4_ecdsa_cipher_suites]. - -cipher_tests() -> - [old_cipher_suites, - cipher_suites_mix, - ciphers_rsa_signed_certs, - ciphers_rsa_signed_certs_openssl_names, - ciphers_dsa_signed_certs, - ciphers_dsa_signed_certs_openssl_names, - chacha_rsa_cipher_suites, - chacha_ecdsa_cipher_suites, - anonymous_cipher_suites, - psk_cipher_suites, - psk_with_hint_cipher_suites, - psk_anon_cipher_suites, - psk_anon_with_hint_cipher_suites, - srp_cipher_suites, - srp_anon_cipher_suites, - srp_dsa_cipher_suites, - des_rsa_cipher_suites, - des_ecdh_rsa_cipher_suites, - default_reject_anonymous]. - -cipher_tests_ec() -> - [ciphers_ecdsa_signed_certs, - ciphers_ecdsa_signed_certs_openssl_names, - ciphers_ecdh_rsa_signed_certs, - ciphers_ecdh_rsa_signed_certs_openssl_names]. - -error_handling_tests()-> - [close_transport_accept, - recv_active, - recv_active_once, - recv_active_n, - recv_error_handling, - call_in_error_state, - close_in_error_state, - abuse_transport_accept_socket, - controlling_process_transport_accept_socket - ]. - -error_handling_tests_tls()-> - [controller_dies, - tls_client_closes_socket, - tls_closed_in_active_once, - tls_tcp_error_propagation_in_active_mode, - tls_tcp_connect, - tls_tcp_connect_big, - tls_dont_crash_on_handshake_garbage + old_cipher_suites, + cipher_suites_mix ]. -rizzo_tests() -> - [rizzo, - no_rizzo_rc4, - rizzo_one_n_minus_one, - rizzo_zero_n, - rizzo_disabled]. +options_tests() -> + [ + unordered_protocol_versions_server, + unordered_protocol_versions_client]. -%%-------------------------------------------------------------------- init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of @@ -292,231 +93,6 @@ end_per_suite(_Config) -> application:stop(crypto). %%-------------------------------------------------------------------- - -init_per_group(GroupName, Config) when GroupName == basic_tls; - GroupName == options_tls; - GroupName == options; - GroupName == basic; - GroupName == session; - GroupName == error_handling_tests_tls - -> - ssl_test_lib:clean_tls_version(Config); -init_per_group(GroupName, Config) -> - ssl_test_lib:clean_tls_version(Config), - case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - _ -> - case ssl_test_lib:sufficient_crypto_support(GroupName) of - true -> - ssl:start(), - Config; - false -> - {skip, "Missing crypto support"} - end - end. - -end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. - -%%-------------------------------------------------------------------- -init_per_testcase(Case, Config) when Case == unordered_protocol_versions_client; - Case == unordered_protocol_versions_server-> - case proplists:get_value(supported, ssl:versions()) of - ['tlsv1.2' | _] -> - ct:timetrap({seconds, 5}), - Config; - _ -> - {skip, "TLS 1.2 need but not supported on this platform"} - end; - -init_per_testcase(protocol_versions, Config) -> - ssl:stop(), - application:load(ssl), - %% For backwards compatibility sslv2 should be filtered out. - application:set_env(ssl, protocol_version, [sslv2, sslv3, tlsv1]), - ssl:start(), - ct:timetrap({seconds, 5}), - Config; - -init_per_testcase(reuse_session_expired, Config) -> - ssl:stop(), - application:load(ssl), - ssl_test_lib:clean_env(), - application:set_env(ssl, session_lifetime, ?EXPIRE), - application:set_env(ssl, session_delay_cleanup_time, 500), - ssl:start(), - ct:timetrap({seconds, 30}), - Config; - -init_per_testcase(empty_protocol_versions, Config) -> - ssl:stop(), - application:load(ssl), - ssl_test_lib:clean_env(), - application:set_env(ssl, protocol_version, []), - ssl:start(), - ct:timetrap({seconds, 5}), - Config; - -init_per_testcase(fallback, Config) -> - case tls_record:highest_protocol_version([]) of - {3, N} when N > 1 -> - ct:timetrap({seconds, 5}), - Config; - _ -> - {skip, "Not relevant if highest supported version is less than 3.2"} - end; - -init_per_testcase(TestCase, Config) when TestCase == client_renegotiate; - TestCase == server_renegotiate; - TestCase == client_secure_renegotiate; - TestCase == client_renegotiate_reused_session; - TestCase == server_renegotiate_reused_session; - TestCase == client_no_wrap_sequence_number; - TestCase == server_no_wrap_sequence_number; - TestCase == renegotiate_dos_mitigate_active; - TestCase == renegotiate_dos_mitigate_passive; - TestCase == renegotiate_dos_mitigate_absolute -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, ?SEC_RENEGOTIATION_TIMEOUT + 5}), - Config; - -init_per_testcase(TestCase, Config) when TestCase == psk_cipher_suites; - TestCase == psk_with_hint_cipher_suites; - TestCase == ciphers_rsa_signed_certs; - TestCase == ciphers_rsa_signed_certs_openssl_names; - TestCase == ciphers_ecdh_rsa_signed_certs_openssl_names; - TestCase == ciphers_ecdh_rsa_signed_certs; - TestCase == ciphers_dsa_signed_certs; - TestCase == ciphers_dsa_signed_certs_openssl_names; - TestCase == anonymous_cipher_suites; - TestCase == ciphers_ecdsa_signed_certs; - TestCase == ciphers_ecdsa_signed_certs_openssl_names; - TestCase == anonymous_cipher_suites; - TestCase == psk_anon_cipher_suites; - TestCase == psk_anon_with_hint_cipher_suites; - TestCase == srp_cipher_suites; - TestCase == srp_anon_cipher_suites; - TestCase == srp_dsa_cipher_suites; - TestCase == des_rsa_cipher_suites; - TestCase == des_ecdh_rsa_cipher_suites; - TestCase == versions_option; - TestCase == tls_tcp_connect_big -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 60}), - Config; - -init_per_testcase(version_option, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 10}), - Config; - -init_per_testcase(reuse_session, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 10}), - Config; - -init_per_testcase(rizzo, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 60}), - Config; - -init_per_testcase(no_rizzo_rc4, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 60}), - Config; - -init_per_testcase(rizzo_one_n_minus_one, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 60}), - rizzo_add_mitigation_option(one_n_minus_one, Config); - -init_per_testcase(rizzo_zero_n, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 60}), - rizzo_add_mitigation_option(zero_n, Config); - -init_per_testcase(rizzo_disabled, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 60}), - rizzo_add_mitigation_option(disabled, Config); - -init_per_testcase(TestCase, Config) when TestCase == no_reuses_session_server_restart_new_cert_file; - TestCase == no_reuses_session_server_restart_new_cert -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 15}), - Config; - -init_per_testcase(prf, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 40}), - case proplists:get_value(tc_group_path, Config) of - [] -> Prop = []; - [Prop] -> Prop - end, - case proplists:get_value(name, Prop) of - undefined -> TlsVersions = [sslv3, tlsv1, 'tlsv1.1', 'tlsv1.2']; - TlsVersion when is_atom(TlsVersion) -> - TlsVersions = [TlsVersion] - end, - PRFS=[md5, sha, sha256, sha384, sha512], - %All are the result of running tls_v1:prf(PrfAlgo, <<>>, <<>>, <<>>, 16) - %with the specified PRF algorithm - ExpectedPrfResults= - [{md5, <<96,139,180,171,236,210,13,10,28,32,2,23,88,224,235,199>>}, - {sha, <<95,3,183,114,33,169,197,187,231,243,19,242,220,228,70,151>>}, - {sha256, <<166,249,145,171,43,95,158,232,6,60,17,90,183,180,0,155>>}, - {sha384, <<153,182,217,96,186,130,105,85,65,103,123,247,146,91,47,106>>}, - {sha512, <<145,8,98,38,243,96,42,94,163,33,53,49,241,4,127,28>>}, - %TLS 1.0 and 1.1 PRF: - {md5sha, <<63,136,3,217,205,123,200,177,251,211,17,229,132,4,173,80>>}], - TestPlan = prf_create_plan(TlsVersions, PRFS, ExpectedPrfResults), - [{prf_test_plan, TestPlan} | Config]; - -init_per_testcase(TestCase, Config) when TestCase == tls_ssl_accept_timeout; - TestCase == tls_client_closes_socket; - TestCase == tls_closed_in_active_once; - TestCase == tls_downgrade -> - ssl:stop(), - ssl:start(), - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 15}), - Config; -init_per_testcase(TestCase, Config) when TestCase == clear_pem_cache; - TestCase == der_input; - TestCase == defaults -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - %% White box test need clean start - ssl:stop(), - ssl:start(), - ct:timetrap({seconds, 20}), - Config; -init_per_testcase(raw_ssl_option, Config) -> - ct:timetrap({seconds, 5}), - case os:type() of - {unix,linux} -> - Config; - _ -> - {skip, "Raw options are platform-specific"} - end; - -init_per_testcase(accept_pool, Config) -> - ct:timetrap({seconds, 5}), - case proplists:get_value(protocol, Config) of - dtls -> - {skip, "Not yet supported on DTLS sockets"}; - _ -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - Config - end; -init_per_testcase(controller_dies, Config) -> - ct:timetrap({seconds, 10}), - Config; init_per_testcase(eccs, Config) -> case ssl:eccs() of [] -> @@ -531,19 +107,8 @@ init_per_testcase(_TestCase, Config) -> ct:timetrap({seconds, 5}), Config. -end_per_testcase(reuse_session_expired, Config) -> - application:unset_env(ssl, session_lifetime), - application:unset_env(ssl, session_delay_cleanup_time), - end_per_testcase(default_action, Config); - -end_per_testcase(Case, Config) when Case == protocol_versions; - Case == empty_protocol_versions-> - application:unset_env(ssl, protocol_versions), - end_per_testcase(default_action, Config); - end_per_testcase(_TestCase, Config) -> Config. - %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- @@ -557,632 +122,76 @@ appup() -> appup(Config) when is_list(Config) -> ok = ?t:appup_test(ssl). %%-------------------------------------------------------------------- -alerts() -> - [{doc, "Test ssl_alert:alert_txt/1"}]. -alerts(Config) when is_list(Config) -> - Descriptions = [?CLOSE_NOTIFY, ?UNEXPECTED_MESSAGE, ?BAD_RECORD_MAC, - ?DECRYPTION_FAILED_RESERVED, ?RECORD_OVERFLOW, ?DECOMPRESSION_FAILURE, - ?HANDSHAKE_FAILURE, ?BAD_CERTIFICATE, ?UNSUPPORTED_CERTIFICATE, - ?CERTIFICATE_REVOKED,?CERTIFICATE_EXPIRED, ?CERTIFICATE_UNKNOWN, - ?ILLEGAL_PARAMETER, ?UNKNOWN_CA, ?ACCESS_DENIED, ?DECODE_ERROR, - ?DECRYPT_ERROR, ?EXPORT_RESTRICTION, ?PROTOCOL_VERSION, - ?INSUFFICIENT_SECURITY, ?INTERNAL_ERROR, ?USER_CANCELED, - ?NO_RENEGOTIATION, ?UNSUPPORTED_EXTENSION, ?CERTIFICATE_UNOBTAINABLE, - ?UNRECOGNISED_NAME, ?BAD_CERTIFICATE_STATUS_RESPONSE, - ?BAD_CERTIFICATE_HASH_VALUE, ?UNKNOWN_PSK_IDENTITY, - 255 %% Unsupported/unknow alert will result in a description too - ], - Alerts = [?ALERT_REC(?WARNING, ?CLOSE_NOTIFY) | - [?ALERT_REC(?FATAL, Desc) || Desc <- Descriptions]], - lists:foreach(fun(Alert) -> - try ssl_alert:alert_txt(Alert) - catch - C:E:T -> - ct:fail({unexpected, {C, E, T}}) - end - end, Alerts). -%%-------------------------------------------------------------------- -alert_details() -> - [{doc, "Test that ssl_alert:alert_txt/1 result contains extendend error description"}]. -alert_details(Config) when is_list(Config) -> - Unique = make_ref(), - UniqueStr = lists:flatten(io_lib:format("~w", [Unique])), - Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Unique), - case string:str(ssl_alert:alert_txt(Alert), UniqueStr) of - 0 -> - ct:fail(error_details_missing); - _ -> - ok - end. - -%%-------------------------------------------------------------------- -alert_details_not_too_big() -> - [{doc, "Test that ssl_alert:alert_txt/1 limits printed depth of extended error description"}]. -alert_details_not_too_big(Config) when is_list(Config) -> - Reason = lists:duplicate(10, lists:duplicate(10, lists:duplicate(10, {some, data}))), - Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Reason), - case length(ssl_alert:alert_txt(Alert)) < 1000 of - true -> - ok; - false -> - ct:fail(ssl_alert_text_too_big) - end. - -%%-------------------------------------------------------------------- -new_options_in_accept() -> - [{doc,"Test that you can set ssl options in ssl_accept/3 and not only in tcp upgrade"}]. -new_options_in_accept(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_dsa_opts, Config), - [_ , _ | ServerSslOpts] = ssl_test_lib:ssl_options(server_opts, Config), %% Remove non ssl opts - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Version = ssl_test_lib:protocol_options(Config, [{tls, sslv3}, {dtls, dtlsv1}]), - Cipher = ssl_test_lib:protocol_options(Config, [{tls, #{key_exchange =>rsa, - cipher => rc4_128, - mac => sha, - prf => default_prf - }}, - {dtls, #{key_exchange =>rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf - }}]), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {ssl_extra_opts, [{versions, [Version]}, - {ciphers,[Cipher]} | ServerSslOpts]}, %% To be set in ssl_accept/3 - {mfa, {?MODULE, connection_info_result, []}}, - {options, proplists:delete(cacertfile, ServerOpts0)}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, connection_info_result, []}}, - {options, [{versions, [Version]}, - {ciphers,[Cipher]} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ServerMsg = ClientMsg = {ok, {Version, Cipher}}, +version_option() -> + [{doc, "Use version option and do no specify ciphers list. Bug specified incorrect ciphers"}]. +version_option(Config) when is_list(Config) -> + Versions = proplists:get_value(supported, ssl:versions()), + [version_option_test(Config, Version) || Version <- Versions]. - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - %%-------------------------------------------------------------------- -handshake_continue() -> - [{doc, "Test API function ssl:handshake_continue/3"}]. -handshake_continue(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}], - Config)}, - {continue_options, proplists:delete(reuseaddr, ServerOpts)} - ]), - - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], - Config)}, - {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%------------------------------------------------------------------ -handshake_continue_timeout() -> - [{doc, "Test API function ssl:handshake_continue/3 with short timeout"}]. -handshake_continue_timeout(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 1}, - {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}], - Config)}, - {continue_options, proplists:delete(reuseaddr, ServerOpts)} - ]), - - Port = ssl_test_lib:inet_port(Server), - - - {connect_failed, _} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, {error,timeout}), - ssl_test_lib:close(Server). - - -%%-------------------------------------------------------------------- -hello_client_cancel() -> - [{doc, "Test API function ssl:handshake_cancel/1 on the client side"}]. -hello_client_cancel(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, proplists:delete(reuseaddr, ServerOpts)}]), - - Port = ssl_test_lib:inet_port(Server), - - %% That is ssl:handshake_cancel returns ok - {connect_failed, ok} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, cancel}]), - ssl_test_lib:check_server_alert(Server, user_canceled). -%%-------------------------------------------------------------------- -hello_server_cancel() -> - [{doc, "Test API function ssl:handshake_cancel/1 on the server side"}]. -hello_server_cancel(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, cancel}]), - - Port = ssl_test_lib:inet_port(Server), - - {connect_failed, _} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), - - ssl_test_lib:check_result(Server, ok). - -%%-------------------------------------------------------------------- -prf() -> - [{doc,"Test that ssl:prf/5 uses the negotiated PRF."}]. -prf(Config) when is_list(Config) -> - TestPlan = proplists:get_value(prf_test_plan, Config), - case TestPlan of - [] -> ct:fail({error, empty_prf_test_plan}); - _ -> lists:foreach(fun(Suite) -> - lists:foreach( - fun(Test) -> - V = proplists:get_value(tls_ver, Test), - C = proplists:get_value(ciphers, Test), - E = proplists:get_value(expected, Test), - P = proplists:get_value(prf, Test), - prf_run_test(Config, V, C, E, P) - end, Suite) - end, TestPlan) - end. - -%%-------------------------------------------------------------------- - -secret_connection_info() -> - [{doc,"Test the API function ssl:connection_information/2"}]. -secret_connection_info(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, secret_connection_info_result, []}}, - {options, ServerOpts}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, secret_connection_info_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, true, Client, true), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- - -connection_information() -> - [{doc,"Test the API function ssl:connection_information/1"}]. -connection_information(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, connection_information_result, []}}, - {options, ServerOpts}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, connection_information_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ServerMsg = ClientMsg = ok, - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -protocol_versions() -> - [{doc,"Test to set a list of protocol versions in app environment."}]. - -protocol_versions(Config) when is_list(Config) -> - basic_test(Config). - -%%-------------------------------------------------------------------- -empty_protocol_versions() -> - [{doc,"Test to set an empty list of protocol versions in app environment."}]. - -empty_protocol_versions(Config) when is_list(Config) -> - basic_test(Config). - -%%-------------------------------------------------------------------- - -controlling_process() -> - [{doc,"Test API function controlling_process/2"}]. - -controlling_process(Config) when is_list(Config) -> +connect_twice() -> + [{doc,""}]. +connect_twice(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientMsg = "Server hello", - ServerMsg = "Client hello", - - Server = ssl_test_lib:start_server([ - {node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - controlling_process_result, [self(), - ServerMsg]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {Client, CSocket} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - controlling_process_result, [self(), - ClientMsg]}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ServerMsg = ssl_test_lib:active_recv(CSocket, length(ServerMsg)), - %% We do not have the TLS server socket but all messages form the client - %% socket are now read, so ramining are form the server socket - ClientMsg = ssl_active_recv(length(ClientMsg)), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -getstat() -> - [{doc,"Test API function getstat/2"}]. - -getstat(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port1 = ssl_test_lib:inet_port(Server1), - Server2 = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port2 = ssl_test_lib:inet_port(Server2), - {ok, ActiveC} = rpc:call(ClientNode, ssl, connect, - [Hostname,Port1,[{active, once}|ClientOpts]]), - {ok, PassiveC} = rpc:call(ClientNode, ssl, connect, - [Hostname,Port2,[{active, false}|ClientOpts]]), - - ct:log("Testcase ~p, Client ~p Servers ~p, ~p ~n", - [self(), self(), Server1, Server2]), - - %% We only check that the values are non-zero initially - %% (due to the handshake), and that sending more changes the values. - - %% Passive socket. - - {ok, InitialStats} = ssl:getstat(PassiveC), - ct:pal("InitialStats ~p~n", [InitialStats]), - [true] = lists:usort([0 =/= proplists:get_value(Name, InitialStats) - || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), - - ok = ssl:send(PassiveC, "Hello world"), - wait_for_send(PassiveC), - {ok, SStats} = ssl:getstat(PassiveC, [send_cnt, send_oct]), - ct:pal("SStats ~p~n", [SStats]), - [true] = lists:usort([proplists:get_value(Name, SStats) =/= proplists:get_value(Name, InitialStats) - || Name <- [send_cnt, send_oct]]), - - %% Active socket. - - {ok, InitialAStats} = ssl:getstat(ActiveC), - ct:pal("InitialAStats ~p~n", [InitialAStats]), - [true] = lists:usort([0 =/= proplists:get_value(Name, InitialAStats) - || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), - - _ = receive - {ssl, ActiveC, _} -> - ok - after - ?SLEEP -> - exit(timeout) - end, - - ok = ssl:send(ActiveC, "Hello world"), - wait_for_send(ActiveC), - {ok, ASStats} = ssl:getstat(ActiveC, [send_cnt, send_oct]), - ct:pal("ASStats ~p~n", [ASStats]), - [true] = lists:usort([proplists:get_value(Name, ASStats) =/= proplists:get_value(Name, InitialAStats) - || Name <- [send_cnt, send_oct]]), - - ok. - -%%-------------------------------------------------------------------- -controller_dies() -> - [{doc,"Test that the socket is closed after controlling process dies"}]. -controller_dies(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientMsg = "Hello server", - ServerMsg = "Hello client", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - controller_dies_result, [self(), - ServerMsg]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - controller_dies_result, [self(), - ClientMsg]}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), - ct:sleep(?SLEEP), %% so that they are connected - - process_flag(trap_exit, true), - - %% Test that clients die - exit(Client, killed), - get_close(Client, ?LINE), - - %% Test that clients die when process disappear - Server ! listen, - Tester = self(), - Connect = fun(Pid) -> - {ok, Socket} = ssl:connect(Hostname, Port, ClientOpts), - %% Make sure server finishes and verification - %% and is in coonection state before - %% killing client - ct:sleep(?SLEEP), - Pid ! {self(), connected, Socket}, - receive die_nice -> normal end - end, - Client2 = spawn_link(fun() -> Connect(Tester) end), - receive {Client2, connected, _Socket} -> Client2 ! die_nice end, - - get_close(Client2, ?LINE), - - %% Test that clients die when the controlling process have changed - Server ! listen, - - Client3 = spawn_link(fun() -> Connect(Tester) end), - Controller = spawn_link(fun() -> receive die_nice -> normal end end), - receive - {Client3, connected, Socket} -> - ok = ssl:controlling_process(Socket, Controller), - Client3 ! die_nice - end, - ct:log("Wating on exit ~p~n",[Client3]), - receive {'EXIT', Client3, normal} -> ok end, - - receive %% Client3 is dead but that doesn't matter, socket should not be closed. - Unexpected -> - ct:log("Unexpected ~p~n",[Unexpected]), - ct:fail({line, ?LINE-1}) - after 1000 -> - ok - end, - Controller ! die_nice, - get_close(Controller, ?LINE), - - %% Test that servers die - Server ! listen, - LastClient = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - controller_dies_result, [self(), - ClientMsg]}}, - {options, ClientOpts}]), - ct:sleep(?SLEEP), %% so that they are connected - - exit(Server, killed), - get_close(Server, ?LINE), - process_flag(trap_exit, false), - ssl_test_lib:close(LastClient). - -%%-------------------------------------------------------------------- -tls_client_closes_socket() -> - [{doc,"Test what happens when client closes socket before handshake is compleated"}]. - -tls_client_closes_socket(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {tcp_options, TcpOpts}, - {ssl_options, ServerOpts}]), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ClientOpts]}]), + Server ! listen, - Connect = fun() -> - {ok, _Socket} = rpc:call(ClientNode, gen_tcp, connect, - [Hostname, Port, [binary]]), - %% Make sure that ssl_accept is called before - %% client process ends and closes socket. - ct:sleep(?SLEEP) - end, - - _Client = spawn_link(Connect), - - ssl_test_lib:check_result(Server, {error,closed}). - -%%-------------------------------------------------------------------- -tls_closed_in_active_once() -> - [{doc, "Test that ssl_closed is delivered in active once with non-empty buffer, check ERL-420."}]. - -tls_closed_in_active_once(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - Port = ssl_test_lib:inet_port(node()), - Server = fun() -> - {ok, Listen} = gen_tcp:listen(Port, TcpOpts), - {ok, TcpServerSocket} = gen_tcp:accept(Listen), - {ok, ServerSocket} = ssl:ssl_accept(TcpServerSocket, ServerOpts), - lists:foreach( - fun(_) -> - ssl:send(ServerSocket, "some random message\r\n") - end, lists:seq(1, 20)), - %% Close TCP instead of SSL socket to trigger the bug: - gen_tcp:close(TcpServerSocket), - gen_tcp:close(Listen) - end, - spawn_link(Server), - {ok, Socket} = ssl:connect(Hostname, Port, [{active, false} | ClientOpts]), - Result = tls_closed_in_active_once_loop(Socket), - ssl:close(Socket), - case Result of - ok -> ok; - _ -> ct:fail(Result) - end. - -tls_closed_in_active_once_loop(Socket) -> - case ssl:setopts(Socket, [{active, once}]) of - ok -> - receive - {ssl, Socket, _} -> - tls_closed_in_active_once_loop(Socket); - {ssl_closed, Socket} -> - ok - after 5000 -> - no_ssl_closed_received - end; - {error, closed} -> - ok - end. -%%-------------------------------------------------------------------- -connect_dist() -> - [{doc,"Test a simple connect as is used by distribution"}]. - -connect_dist(Config) when is_list(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_kc_opts, Config), - ClientOpts = [{ssl_imp, new},{active, false}, {packet,4}|ClientOpts0], - ServerOpts0 = ssl_test_lib:ssl_options(server_kc_opts, Config), - ServerOpts = [{ssl_imp, new},{active, false}, {packet,4}|ServerOpts0], + {Client1, #sslsocket{}} = + ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ClientOpts]}]), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, connect_dist_s, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, connect_dist_c, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:check_result(Server, ok, Client1, ok), ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -clear_pem_cache() -> - [{doc,"Test that internal reference tabel is cleaned properly even when " - " the PEM cache is cleared" }]. -clear_pem_cache(Config) when is_list(Config) -> - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - [_,{FilRefDb, _} |_] = element(6, State), - {Server, Client} = basic_verify_test_no_close(Config), - CountReferencedFiles = fun({_, -1}, Acc) -> - Acc; - ({_, N}, Acc) -> - N + Acc - end, - - 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), - ssl:clear_pem_cache(), - _ = sys:get_status(whereis(ssl_manager)), - {Server1, Client1} = basic_verify_test_no_close(Config), - 4 = ets:foldl(CountReferencedFiles, 0, FilRefDb), - ssl_test_lib:close(Server), ssl_test_lib:close(Client), - ct:sleep(2000), - _ = sys:get_status(whereis(ssl_manager)), - 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1), - ct:sleep(2000), - _ = sys:get_status(whereis(ssl_manager)), - 0 = ets:foldl(CountReferencedFiles, 0, FilRefDb). + ssl_test_lib:close(Client1). +defaults(Config) when is_list(Config)-> + Versions = ssl:versions(), + true = lists:member(sslv3, proplists:get_value(available, Versions)), + false = lists:member(sslv3, proplists:get_value(supported, Versions)), + true = lists:member('tlsv1', proplists:get_value(available, Versions)), + false = lists:member('tlsv1', proplists:get_value(supported, Versions)), + true = lists:member('tlsv1.1', proplists:get_value(available, Versions)), + false = lists:member('tlsv1.1', proplists:get_value(supported, Versions)), + true = lists:member('tlsv1.2', proplists:get_value(available, Versions)), + true = lists:member('tlsv1.2', proplists:get_value(supported, Versions)), + false = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites()), + true = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites(all)), + false = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites()), + true = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites(all)), + false = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites()), + true = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites(all)), + true = lists:member('dtlsv1.2', proplists:get_value(available_dtls, Versions)), + true = lists:member('dtlsv1', proplists:get_value(available_dtls, Versions)), + true = lists:member('dtlsv1.2', proplists:get_value(supported_dtls, Versions)), + false = lists:member('dtlsv1', proplists:get_value(supported_dtls, Versions)). -%%-------------------------------------------------------------------- fallback() -> [{doc, "Test TLS_FALLBACK_SCSV downgrade prevention"}]. @@ -1208,7 +217,6 @@ fallback(Config) when is_list(Config) -> | ClientOpts]}]), ssl_test_lib:check_server_alert(Server, Client, inappropriate_fallback). - %%-------------------------------------------------------------------- cipher_format() -> [{doc, "Test that cipher conversion from maps | tuples | stings to binarys works"}]. @@ -1222,175 +230,6 @@ cipher_format(Config) when is_list(Config) -> ssl:close(Socket2). %%-------------------------------------------------------------------- -suite_to_str() -> - [{doc, "Test that the suite_to_str API works"}]. -suite_to_str(Config) when is_list(Config) -> - "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" = - ssl:suite_to_str(#{key_exchange => null, - cipher => null, - mac => null, - prf => null}), - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" = - ssl:suite_to_str(#{key_exchange => ecdhe_ecdsa, - cipher => aes_128_gcm, - mac => aead, - prf => sha256}), - "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" = - ssl:suite_to_str(#{key_exchange => ecdh_rsa, - cipher => aes_128_cbc, - mac => sha256, - prf => sha256}). - -%%-------------------------------------------------------------------- - -peername() -> - [{doc,"Test API function peername/1"}]. - -peername(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peername_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, peername_result, []}}, - {options, [{port, 0} | ClientOpts]}]), - - ClientPort = ssl_test_lib:inet_port(Client), - ServerIp = ssl_test_lib:node_to_hostip(ServerNode), - ClientIp = ssl_test_lib:node_to_hostip(ClientNode), - ServerMsg = {ok, {ClientIp, ClientPort}}, - ClientMsg = {ok, {ServerIp, Port}}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -peercert() -> - [{doc,"Test API function peercert/1"}]. -peercert(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ClientOpts}]), - - CertFile = proplists:get_value(certfile, ServerOpts), - [{'Certificate', BinCert, _}]= ssl_test_lib:pem_to_der(CertFile), - - ServerMsg = {error, no_peercert}, - ClientMsg = {ok, BinCert}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -peercert_result(Socket) -> - ssl:peercert(Socket). -%%-------------------------------------------------------------------- - -peercert_with_client_cert() -> - [{doc,"Test API function peercert/1"}]. -peercert_with_client_cert(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ClientOpts}]), - - ServerCertFile = proplists:get_value(certfile, ServerOpts), - [{'Certificate', ServerBinCert, _}]= ssl_test_lib:pem_to_der(ServerCertFile), - ClientCertFile = proplists:get_value(certfile, ClientOpts), - [{'Certificate', ClientBinCert, _}]= ssl_test_lib:pem_to_der(ClientCertFile), - - ServerMsg = {ok, ClientBinCert}, - ClientMsg = {ok, ServerBinCert}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -sockname() -> - [{doc,"Test API function sockname/1"}]. -sockname(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, sockname_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, sockname_result, []}}, - {options, [{port, 0} | ClientOpts]}]), - - ClientPort = ssl_test_lib:inet_port(Client), - ServerIp = - case proplists:get_value(protocol, Config) of - dtls -> - %% DTLS sockets are not connected on the server side, - %% so we can only get a ClientIP, ServerIP will always be 0.0.0.0 - {0,0,0,0}; - _ -> - ssl_test_lib:node_to_hostip(ServerNode) - end, - - ClientIp = ssl_test_lib:node_to_hostip(ClientNode), - ServerMsg = {ok, {ServerIp, Port}}, - ClientMsg = {ok, {ClientIp, ClientPort}}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -sockname_result(S) -> - ssl:sockname(S). - -%%-------------------------------------------------------------------- cipher_suites() -> [{doc,"Test API function cipher_suites/2, filter_cipher_suites/2" @@ -1487,2884 +326,95 @@ cipher_suites_mix(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -tls_socket_options() -> - [{doc,"Test API function getopts/2 and setopts/2"}]. +unordered_protocol_versions_server() -> + [{doc,"Test that the highest protocol is selected even" + " when it is not first in the versions list."}]. -tls_socket_options(Config) when is_list(Config) -> +unordered_protocol_versions_server(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Values = [{mode, list}, {packet, 0}, {header, 0}, - {active, true}], - %% Shall be the reverse order of Values! - Options = [active, header, packet, mode], - - NewValues = [{mode, binary}, {active, once}], - %% Shall be the reverse order of NewValues! - NewOptions = [active, mode], - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - - {ok, Listen} = ssl:listen(0, ServerOpts), - {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), - ok = ssl:setopts(Listen, [{mode, binary}]), - {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), - {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), - ssl:close(Listen). - -tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> - %% Test get/set emulated opts - {ok, DefaultValues} = ssl:getopts(Socket, Options), - ssl:setopts(Socket, NewValues), - {ok, NewValues} = ssl:getopts(Socket, NewOptions), - %% Test get/set inet opts - {ok,[{nodelay,false}]} = ssl:getopts(Socket, [nodelay]), - ssl:setopts(Socket, [{nodelay, true}]), - {ok,[{nodelay, true}]} = ssl:getopts(Socket, [nodelay]), - {ok, All} = ssl:getopts(Socket, []), - ct:log("All opts ~p~n", [All]), - ok. - - -%%-------------------------------------------------------------------- -socket_options() -> - [{doc,"Test API function getopts/2 and setopts/2"}]. + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), -socket_options(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Values = [{mode, list}, {active, true}], - %% Shall be the reverse order of Values! - Options = [active, mode], - - NewValues = [{mode, binary}, {active, once}], - %% Shall be the reverse order of NewValues! - NewOptions = [active, mode], - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - - {ok, Listen} = ssl:listen(0, ServerOpts), - {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), - ok = ssl:setopts(Listen, [{mode, binary}]), - {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), - {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), - ssl:close(Listen). - - -socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> - %% Test get/set emulated opts - {ok, DefaultValues} = ssl:getopts(Socket, Options), - ssl:setopts(Socket, NewValues), - {ok, NewValues} = ssl:getopts(Socket, NewOptions), - %% Test get/set inet opts - {ok,[{reuseaddr, _}]} = ssl:getopts(Socket, [reuseaddr]), - {ok, All} = ssl:getopts(Socket, []), - ct:log("All opts ~p~n", [All]), - ok. - - -%%-------------------------------------------------------------------- -invalid_inet_get_option() -> - [{doc,"Test handling of invalid inet options in getopts"}]. - -invalid_inet_get_option(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, get_invalid_inet_option, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -invalid_inet_get_option_not_list() -> - [{doc,"Test handling of invalid type in getopts"}]. - -invalid_inet_get_option_not_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, get_invalid_inet_option_not_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -get_invalid_inet_option_not_list(Socket) -> - {error, {options, {socket_options, some_invalid_atom_here}}} - = ssl:getopts(Socket, some_invalid_atom_here), - ok. - -%%-------------------------------------------------------------------- -invalid_inet_get_option_improper_list() -> - [{doc,"Test handling of invalid type in getopts"}]. - -invalid_inet_get_option_improper_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, get_invalid_inet_option_improper_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -get_invalid_inet_option_improper_list(Socket) -> - {error, {options, {socket_options, foo,_}}} = ssl:getopts(Socket, [packet | foo]), - ok. - -%%-------------------------------------------------------------------- -invalid_inet_set_option() -> - [{doc,"Test handling of invalid inet options in setopts"}]. - -invalid_inet_set_option(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, set_invalid_inet_option, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -set_invalid_inet_option(Socket) -> - {error, {options, {socket_options, {packet, foo}}}} = ssl:setopts(Socket, [{packet, foo}]), - {error, {options, {socket_options, {header, foo}}}} = ssl:setopts(Socket, [{header, foo}]), - {error, {options, {socket_options, {active, foo}}}} = ssl:setopts(Socket, [{active, foo}]), - {error, {options, {socket_options, {mode, foo}}}} = ssl:setopts(Socket, [{mode, foo}]), - ok. -%%-------------------------------------------------------------------- -invalid_inet_set_option_not_list() -> - [{doc,"Test handling of invalid type in setopts"}]. - -invalid_inet_set_option_not_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, set_invalid_inet_option_not_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -set_invalid_inet_option_not_list(Socket) -> - {error, {options, {not_a_proplist, some_invalid_atom_here}}} - = ssl:setopts(Socket, some_invalid_atom_here), - ok. - -%%-------------------------------------------------------------------- -invalid_inet_set_option_improper_list() -> - [{doc,"Test handling of invalid tye in setopts"}]. - -invalid_inet_set_option_improper_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, set_invalid_inet_option_improper_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -set_invalid_inet_option_improper_list(Socket) -> - {error, {options, {not_a_proplist, [{packet, 0} | {foo, 2}]}}} = - ssl:setopts(Socket, [{packet, 0} | {foo, 2}]), - ok. - -%%-------------------------------------------------------------------- -tls_misc_ssl_options() -> - [{doc,"Test what happens when we give valid options"}]. - -tls_misc_ssl_options(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% Check that ssl options not tested elsewhere are filtered away e.i. not passed to inet. - TestOpts = [{depth, 1}, - {key, undefined}, - {password, []}, - {reuse_session, fun(_,_,_,_) -> true end}, - {cb_info, {gen_tcp, tcp, tcp_closed, tcp_error}}], - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, TestOpts ++ ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, TestOpts ++ ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -ssl_options_not_proplist() -> - [{doc,"Test what happens if an option is not a key value tuple"}]. - -ssl_options_not_proplist(Config) when is_list(Config) -> - BadOption = {client_preferred_next_protocols, - client, [<<"spdy/3">>,<<"http/1.1">>], <<"http/1.1">>}, - {option_not_a_key_value_tuple, BadOption} = - ssl:connect("twitter.com", 443, [binary, {active, false}, - BadOption]). - -%%-------------------------------------------------------------------- -raw_ssl_option() -> - [{doc,"Ensure that a single 'raw' option is passed to ssl:listen correctly."}]. - -raw_ssl_option(Config) when is_list(Config) -> - % 'raw' option values are platform-specific; these are the Linux values: - IpProtoTcp = 6, - % Use TCP_KEEPIDLE, because (e.g.) TCP_MAXSEG can't be read back reliably. - TcpKeepIdle = 4, - KeepAliveTimeSecs = 55, - LOptions = [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}], - {ok, LSocket} = ssl:listen(0, LOptions), - % Per http://www.erlang.org/doc/man/inet.html#getopts-2, we have to specify - % exactly which raw option we want, and the size of the buffer. - {ok, [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}]} = ssl:getopts(LSocket, [{raw, IpProtoTcp, TcpKeepIdle, 4}]). - - -%%-------------------------------------------------------------------- -versions() -> - [{doc,"Test API function versions/0"}]. - -versions(Config) when is_list(Config) -> - [_|_] = Versions = ssl:versions(), - ct:log("~p~n", [Versions]). - - -%%-------------------------------------------------------------------- -eccs() -> - [{doc, "Test API functions eccs/0 and eccs/1"}]. - -eccs(Config) when is_list(Config) -> - [_|_] = All = ssl:eccs(), - [] = SSL3 = ssl:eccs(sslv3), - [_|_] = Tls = ssl:eccs(tlsv1), - [_|_] = Tls1 = ssl:eccs('tlsv1.1'), - [_|_] = Tls2 = ssl:eccs('tlsv1.2'), - [_|_] = Tls1 = ssl:eccs('dtlsv1'), - [_|_] = Tls2 = ssl:eccs('dtlsv1.2'), - %% ordering is currently unverified by the test - true = lists:sort(All) =:= lists:usort(SSL3 ++ Tls ++ Tls1 ++ Tls2), - ok. - -%%-------------------------------------------------------------------- -send_recv() -> - [{doc,""}]. -send_recv(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_send_close() -> - [{doc,""}]. -tls_send_close(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - {ok, TcpS} = rpc:call(ClientNode, gen_tcp, connect, - [Hostname,Port,[binary, {active, false}]]), - {ok, SslS} = rpc:call(ClientNode, ssl, connect, - [TcpS,[{active, false}|ClientOpts]]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), self(), Server]), - ok = ssl:send(SslS, "Hello world"), - {ok,<<"Hello world">>} = ssl:recv(SslS, 11), - gen_tcp:close(TcpS), - {error, _} = ssl:send(SslS, "Hello world"). - -%%-------------------------------------------------------------------- -version_option() -> - [{doc, "Use version option and do no specify ciphers list. Bug specified incorrect ciphers"}]. -version_option(Config) when is_list(Config) -> - Versions = proplists:get_value(supported, ssl:versions()), - [version_option_test(Config, Version) || Version <- Versions]. - -%%-------------------------------------------------------------------- -close_transport_accept() -> - [{doc,"Tests closing ssl socket when waiting on ssl:transport_accept/1"}]. - -close_transport_accept(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Port = 0, - Opts = [{active, false} | ServerOpts], - {ok, ListenSocket} = rpc:call(ServerNode, ssl, listen, [Port, Opts]), - spawn_link(fun() -> - ct:sleep(?SLEEP), - rpc:call(ServerNode, ssl, close, [ListenSocket]) - end), - case rpc:call(ServerNode, ssl, transport_accept, [ListenSocket]) of - {error, closed} -> - ok; - Other -> - exit({?LINE, Other}) - end. -%%-------------------------------------------------------------------- -recv_active() -> - [{doc,"Test recv on active socket"}]. - -recv_active(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, try_recv_active, []}}, - {options, [{active, true} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, try_recv_active, []}}, - {options, [{active, true} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -recv_active_once() -> - [{doc,"Test recv on active (once) socket"}]. - -recv_active_once(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, once} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, once} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -recv_active_n() -> - [{doc,"Test recv on active (n) socket"}]. - -recv_active_n(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, 1} | ServerOpts]}]), + {mfa, {?MODULE, protocol_info_result, []}}, + {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, 1} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -%% Test case adapted from gen_tcp_misc_SUITE. -active_n() -> - [{doc,"Test {active,N} option"}]. - -active_n(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - Port = ssl_test_lib:inet_port(node()), - N = 3, - LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])), - [{active,N}] = ok(ssl:getopts(LS, [active])), - active_n_common(LS, N), - Self = self(), - spawn_link(fun() -> - S0 = ok(ssl:transport_accept(LS)), - {ok, S} = ssl:handshake(S0), - ok = ssl:setopts(S, [{active,N}]), - [{active,N}] = ok(ssl:getopts(S, [active])), - ssl:controlling_process(S, Self), - Self ! {server, S} - end), - C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])), - [{active,N}] = ok(ssl:getopts(C, [active])), - S = receive - {server, S0} -> S0 - after - 1000 -> - exit({error, connect}) - end, - active_n_common(C, N), - active_n_common(S, N), - ok = ssl:setopts(C, [{active,N}]), - ok = ssl:setopts(S, [{active,N}]), - ReceiveMsg = fun(Socket, Msg) -> - receive - {ssl,Socket,Msg} -> - ok; - {ssl,Socket,Begin} -> - receive - {ssl,Socket,End} -> - Msg = Begin ++ End, - ok - after 1000 -> - exit(timeout) - end - after 1000 -> - exit(timeout) - end - end, - repeat(3, fun(I) -> - Msg = "message "++integer_to_list(I), - ok = ssl:send(C, Msg), - ReceiveMsg(S, Msg), - ok = ssl:send(S, Msg), - ReceiveMsg(C, Msg) - end), - receive - {ssl_passive,S} -> - [{active,false}] = ok(ssl:getopts(S, [active])) - after - 1000 -> - exit({error,ssl_passive}) - end, - receive - {ssl_passive,C} -> - [{active,false}] = ok(ssl:getopts(C, [active])) - after - 1000 -> - exit({error,ssl_passive}) - end, - LS2 = ok(ssl:listen(0, [{active,0}])), - receive - {ssl_passive,LS2} -> - [{active,false}] = ok(ssl:getopts(LS2, [active])) - after - 1000 -> - exit({error,ssl_passive}) - end, - ok = ssl:close(LS2), - ok = ssl:close(C), - ok = ssl:close(S), - ok = ssl:close(LS), - ok. - -active_n_common(S, N) -> - ok = ssl:setopts(S, [{active,-N}]), - receive - {ssl_passive, S} -> ok - after - 1000 -> - error({error,ssl_passive_failure}) - end, - [{active,false}] = ok(ssl:getopts(S, [active])), - ok = ssl:setopts(S, [{active,0}]), - receive - {ssl_passive, S} -> ok - after - 1000 -> - error({error,ssl_passive_failure}) - end, - ok = ssl:setopts(S, [{active,32767}]), - {error,{options,_}} = ssl:setopts(S, [{active,1}]), - {error,{options,_}} = ssl:setopts(S, [{active,-32769}]), - ok = ssl:setopts(S, [{active,-32768}]), - receive - {ssl_passive, S} -> ok - after - 1000 -> - error({error,ssl_passive_failure}) - end, - [{active,false}] = ok(ssl:getopts(S, [active])), - ok = ssl:setopts(S, [{active,N}]), - ok = ssl:setopts(S, [{active,true}]), - [{active,true}] = ok(ssl:getopts(S, [active])), - receive - _ -> error({error,active_n}) - after - 0 -> - ok - end, - ok = ssl:setopts(S, [{active,N}]), - ok = ssl:setopts(S, [{active,once}]), - [{active,once}] = ok(ssl:getopts(S, [active])), - receive - _ -> error({error,active_n}) - after - 0 -> - ok - end, - {error,{options,_}} = ssl:setopts(S, [{active,32768}]), - ok = ssl:setopts(S, [{active,false}]), - [{active,false}] = ok(ssl:getopts(S, [active])), - ok. - -ok({ok,V}) -> V. - -repeat(N, Fun) -> - repeat(N, N, Fun). - -repeat(N, T, Fun) when is_integer(N), N > 0 -> - Fun(T-N), - repeat(N-1, T, Fun); -repeat(_, _, _) -> - ok. - -%%-------------------------------------------------------------------- -dh_params() -> - [{doc,"Test to specify DH-params file in server."}]. - -dh_params(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - DataDir = proplists:get_value(data_dir, Config), - DHParamFile = filename:join(DataDir, "dHParam.pem"), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{dhfile, DHParamFile} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{ciphers,[{dhe_rsa,aes_256_cbc,sha}]} | - ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_upgrade() -> - [{doc,"Test that you can upgrade an tcp connection to an ssl connection"}]. - -tls_upgrade(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - upgrade_result, []}}, - {tcp_options, - [{active, false} | TcpOpts]}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, - {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, upgrade_result, []}}, - {tcp_options, [binary]}, - {ssl_options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -upgrade_result(Socket) -> - ssl:setopts(Socket, [{active, true}]), - ok = ssl:send(Socket, "Hello world"), - %% Make sure binary is inherited from tcp socket and that we do - %% not get the list default! - receive - {ssl, _, <<"H">>} -> - receive - {ssl, _, <<"ello world">>} -> - ok - end; - {ssl, _, <<"Hello world">>} -> - ok - end. - -%%-------------------------------------------------------------------- -tls_upgrade_with_timeout() -> - [{doc,"Test ssl_accept/3"}]. - -tls_upgrade_with_timeout(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {?MODULE, - upgrade_result, []}}, - {tcp_options, - [{active, false} | TcpOpts]}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, - {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, upgrade_result, []}}, - {tcp_options, TcpOpts}, - {ssl_options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_downgrade() -> - [{doc,"Test that you can downgarde an ssl connection to an tcp connection"}]. -tls_downgrade(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_downgrade_result, [self()]}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_downgrade_result, [self()]}}, - {options, [{active, false} |ClientOpts]}]), - - - ssl_test_lib:check_result(Server, ready, Client, ready), - - Server ! go, - Client ! go, - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -close_with_timeout() -> - [{doc,"Test normal (not downgrade) ssl:close/2"}]. -close_with_timeout(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_close, []}}, - {options,[{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_close, []}}, - {options, [{active, false} |ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok). - - -%%-------------------------------------------------------------------- -tls_tcp_connect() -> - [{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}]. - -tls_tcp_connect(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}, {active, false}], - - Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {?MODULE, dummy, []}}, - {tcp_options, TcpOpts}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), - ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), - gen_tcp:send(Socket, "<SOME GARBLED NON SSL MESSAGE>"), - - receive - {tcp_closed, Socket} -> - receive - {Server, {error, Error}} -> - ct:log("Error ~p", [Error]) - end - end. -%%-------------------------------------------------------------------- -tls_tcp_connect_big() -> - [{doc,"Test what happens when a tcp tries to connect, i,e. a bad big (ssl) packet is sent first"}]. - -tls_tcp_connect_big(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Rand = crypto:strong_rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), - Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {?MODULE, dummy, []}}, - {tcp_options, TcpOpts}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), - ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), - - gen_tcp:send(Socket, <<?BYTE(0), - ?BYTE(3), ?BYTE(1), ?UINT16(?MAX_CIPHER_TEXT_LENGTH), Rand/binary>>), - - receive - {tcp_closed, Socket} -> - receive - {Server, {error, timeout}} -> - ct:fail("hangs"); - {Server, {error, Error}} -> - ct:log("Error ~p", [Error]); - {'EXIT', Server, _} -> - ok - end - end. - -%%-------------------------------------------------------------------- -ipv6() -> - [{require, ipv6_hosts}, - {doc,"Test ipv6."}]. -ipv6(Config) when is_list(Config) -> - {ok, Hostname0} = inet:gethostname(), - - case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of - true -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = - ssl_test_lib:run_where(Config, ipv6), - Server = ssl_test_lib:start_server([{node, ServerNode}, - {port, 0}, {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, - [inet6, {active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, - [inet6, {active, false} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client); - false -> - {skip, "Host does not support IPv6"} - end. - -%%-------------------------------------------------------------------- - -invalid_keyfile() -> - [{doc,"Test what happens with an invalid key file"}]. -invalid_keyfile(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - BadOpts = ssl_test_lib:ssl_options(server_bad_key, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, BadOpts}]), - - Port = ssl_test_lib:inet_port(Server), - - Client = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, {options, ClientOpts}]), + {from, self()}, + {mfa, {?MODULE, protocol_info_result, []}}, + {options, ClientOpts}]), - File = proplists:get_value(keyfile,BadOpts), - ssl_test_lib:check_result(Server, {error,{options, {keyfile, File, {error,enoent}}}}, Client, - {error, closed}). + ServerMsg = ClientMsg = {ok,'tlsv1.2'}, + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). %%-------------------------------------------------------------------- +unordered_protocol_versions_client() -> + [{doc,"Test that the highest protocol is selected even" + " when it is not first in the versions list."}]. -invalid_certfile() -> - [{doc,"Test what happens with an invalid cert file"}]. - -invalid_certfile(Config) when is_list(Config) -> +unordered_protocol_versions_client(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerBadOpts = ssl_test_lib:ssl_options(server_bad_cert, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerBadOpts}]), - - Port = ssl_test_lib:inet_port(Server), - - Client = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - File = proplists:get_value(certfile, ServerBadOpts), - ssl_test_lib:check_result(Server, {error,{options, {certfile, File, {error,enoent}}}}, - Client, {error, closed}). - - -%%-------------------------------------------------------------------- -invalid_cacertfile() -> - [{doc,"Test what happens with an invalid cacert file"}]. - -invalid_cacertfile(Config) when is_list(Config) -> - ClientOpts = [{reuseaddr, true}|ssl_test_lib:ssl_options(client_opts, Config)], - ServerBadOpts = [{reuseaddr, true}|ssl_test_lib:ssl_options(server_bad_ca, Config)], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server0 = - ssl_test_lib:start_server_error([{node, ServerNode}, - {port, 0}, {from, self()}, - {options, ServerBadOpts}]), - - Port0 = ssl_test_lib:inet_port(Server0), - - - Client0 = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port0}, {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - - File0 = proplists:get_value(cacertfile, ServerBadOpts), - - ssl_test_lib:check_result(Server0, {error, {options, {cacertfile, File0,{error,enoent}}}}, - Client0, {error, closed}), - - File = File0 ++ "do_not_exit.pem", - ServerBadOpts1 = [{cacertfile, File}|proplists:delete(cacertfile, ServerBadOpts)], - - Server1 = - ssl_test_lib:start_server_error([{node, ServerNode}, - {port, 0}, {from, self()}, - {options, ServerBadOpts1}]), - - Port1 = ssl_test_lib:inet_port(Server1), - - Client1 = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port1}, {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - - - ssl_test_lib:check_result(Server1, {error, {options, {cacertfile, File,{error,enoent}}}}, - Client1, {error, closed}), - ok. - - - -%%-------------------------------------------------------------------- -invalid_options() -> - [{doc,"Test what happens when we give invalid options"}]. - -invalid_options(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) -> - ssl_test_lib:check_result(Server, - {error, {options, {sslv2, Option}}}, - Client, - {error, {options, {sslv2, Option}}}); - (Client, Server, Option) -> - ssl_test_lib:check_result(Server, - {error, {options, Option}}, - Client, - {error, {options, Option}}) - end, - - TestOpts = - [{versions, [sslv2, sslv3]}, - {verify, 4}, - {verify_fun, function}, - {fail_if_no_peer_cert, 0}, - {verify_client_once, 1}, - {depth, four}, - {certfile, 'cert.pem'}, - {keyfile,'key.pem' }, - {password, foo}, - {cacertfile, ""}, - {dhfile,'dh.pem' }, - {ciphers, [{foo, bar, sha, ignore}]}, - {reuse_session, foo}, - {reuse_sessions, 0}, - {renegotiate_at, "10"}, - {mode, depech}, - {packet, 8.0}, - {packet_size, "2"}, - {header, a}, - {active, trice}, - {key, 'key.pem' }], - - [begin - Server = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [TestOpt | ServerOpts]}]), - %% Will never reach a point where port is used. - Client = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, - {host, Hostname}, {from, self()}, - {options, [TestOpt | ClientOpts]}]), - Check(Client, Server, TestOpt), - ok - end || TestOpt <- TestOpts], - ok. + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), -%%-------------------------------------------------------------------- -tls_shutdown() -> - [{doc,"Test API function ssl:shutdown/2"}]. -tls_shutdown(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, tls_shutdown_result, [server]}}, - {options, [{exit_on_close, false}, - {active, false} | ServerOpts]}]), + {mfa, {?MODULE, protocol_info_result, []}}, + {options, ServerOpts }]), Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, - {?MODULE, tls_shutdown_result, [client]}}, - {options, - [{exit_on_close, false}, - {active, false} | ClientOpts]}]), - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_shutdown_write() -> - [{doc,"Test API function ssl:shutdown/2 with option write."}]. -tls_shutdown_write(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_shutdown_write_result, [server]}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_shutdown_write_result, [client]}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, {error, closed}). - -%%-------------------------------------------------------------------- -tls_shutdown_both() -> - [{doc,"Test API function ssl:shutdown/2 with option both."}]. -tls_shutdown_both(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, tls_shutdown_both_result, [server]}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_shutdown_both_result, [client]}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, {error, closed}). - -%%-------------------------------------------------------------------- -tls_shutdown_error() -> - [{doc,"Test ssl:shutdown/2 error handling"}]. -tls_shutdown_error(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - Port = ssl_test_lib:inet_port(node()), - {ok, Listen} = ssl:listen(Port, ServerOpts), - {error, enotconn} = ssl:shutdown(Listen, read_write), - ok = ssl:close(Listen), - {error, closed} = ssl:shutdown(Listen, read_write). - -%%------------------------------------------------------------------- -ciphers_rsa_signed_certs() -> - [{doc,"Test all rsa ssl cipher suites in highest support ssl/tls version"}]. - -ciphers_rsa_signed_certs(Config) when is_list(Config) -> - Ciphers = ssl_test_lib:rsa_suites(crypto), - run_suites(Ciphers, Config, rsa). -%%------------------------------------------------------------------- -ciphers_rsa_signed_certs_openssl_names() -> - [{doc,"Test all rsa ssl cipher suites in highest support ssl/tls version"}]. - -ciphers_rsa_signed_certs_openssl_names(Config) when is_list(Config) -> - Ciphers = ssl_test_lib:openssl_rsa_suites(), - run_suites(Ciphers, Config, rsa). - -%%------------------------------------------------------------------- -ciphers_dsa_signed_certs() -> - [{doc,"Test all dsa ssl cipher suites in highest support ssl/tls version"}]. - -ciphers_dsa_signed_certs(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:dsa_suites(NVersion), - run_suites(Ciphers, Config, dsa). -%%------------------------------------------------------------------- -ciphers_dsa_signed_certs_openssl_names() -> - [{doc,"Test all dsa ssl cipher suites in highest support ssl/tls version"}]. - -ciphers_dsa_signed_certs_openssl_names(Config) when is_list(Config) -> - Ciphers = ssl_test_lib:openssl_dsa_suites(), - run_suites(Ciphers, Config, dsa). - -%%------------------------------------------------------------------- -chacha_rsa_cipher_suites()-> - [{doc,"Test the cacha with ECDSA signed certs ciphersuites"}]. -chacha_rsa_cipher_suites(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = [S || {KeyEx,_,_} = S <- ssl_test_lib:chacha_suites(NVersion), - KeyEx == ecdhe_rsa, KeyEx == dhe_rsa], - run_suites(Ciphers, Config, chacha_ecdsa). - -%%------------------------------------------------------------------- -chacha_ecdsa_cipher_suites()-> - [{doc,"Test the cacha with ECDSA signed certs ciphersuites"}]. -chacha_ecdsa_cipher_suites(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = [S || {ecdhe_ecdsa,_,_} = S <- ssl_test_lib:chacha_suites(NVersion)], - run_suites(Ciphers, Config, chacha_rsa). -%%----------------------------------------------------------------- -anonymous_cipher_suites()-> - [{doc,"Test the anonymous ciphersuites"}]. -anonymous_cipher_suites(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(NVersion), - run_suites(Ciphers, Config, anonymous). -%%------------------------------------------------------------------- -psk_cipher_suites() -> - [{doc, "Test the PSK ciphersuites WITHOUT server supplied identity hint"}]. -psk_cipher_suites(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:psk_suites(NVersion), - run_suites(Ciphers, Config, psk). -%%------------------------------------------------------------------- -psk_with_hint_cipher_suites()-> - [{doc, "Test the PSK ciphersuites WITH server supplied identity hint"}]. -psk_with_hint_cipher_suites(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:psk_suites(NVersion), - run_suites(Ciphers, Config, psk_with_hint). -%%------------------------------------------------------------------- -psk_anon_cipher_suites() -> - [{doc, "Test the anonymous PSK ciphersuites WITHOUT server supplied identity hint"}]. -psk_anon_cipher_suites(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:psk_anon_suites(NVersion), - run_suites(Ciphers, Config, psk_anon). -%%------------------------------------------------------------------- -psk_anon_with_hint_cipher_suites()-> - [{doc, "Test the anonymous PSK ciphersuites WITH server supplied identity hint"}]. -psk_anon_with_hint_cipher_suites(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:psk_anon_suites(NVersion), - run_suites(Ciphers, Config, psk_anon_with_hint). -%%------------------------------------------------------------------- -srp_cipher_suites()-> - [{doc, "Test the SRP ciphersuites"}]. -srp_cipher_suites(Config) when is_list(Config) -> - Ciphers = ssl_test_lib:srp_suites(), - run_suites(Ciphers, Config, srp). -%%------------------------------------------------------------------- -srp_anon_cipher_suites()-> - [{doc, "Test the anonymous SRP ciphersuites"}]. -srp_anon_cipher_suites(Config) when is_list(Config) -> - Ciphers = ssl_test_lib:srp_anon_suites(), - run_suites(Ciphers, Config, srp_anon). -%%------------------------------------------------------------------- -srp_dsa_cipher_suites()-> - [{doc, "Test the SRP DSA ciphersuites"}]. -srp_dsa_cipher_suites(Config) when is_list(Config) -> - Ciphers = ssl_test_lib:srp_dss_suites(), - run_suites(Ciphers, Config, srp_dsa). -%%------------------------------------------------------------------- -rc4_rsa_cipher_suites()-> - [{doc, "Test the RC4 ciphersuites"}]. -rc4_rsa_cipher_suites(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = [S || {rsa,_,_} = S <- ssl_test_lib:rc4_suites(NVersion)], - run_suites(Ciphers, Config, rc4_rsa). -%------------------------------------------------------------------- -rc4_ecdh_rsa_cipher_suites()-> - [{doc, "Test the RC4 ciphersuites"}]. -rc4_ecdh_rsa_cipher_suites(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = [S || {ecdh_rsa,_,_} = S <- ssl_test_lib:rc4_suites(NVersion)], - run_suites(Ciphers, Config, rc4_ecdh_rsa). - -%%------------------------------------------------------------------- -rc4_ecdsa_cipher_suites()-> - [{doc, "Test the RC4 ciphersuites"}]. -rc4_ecdsa_cipher_suites(Config) when is_list(Config) -> - NVersion = tls_record:highest_protocol_version([]), - Ciphers = [S || {ecdhe_ecdsa,_,_} = S <- ssl_test_lib:rc4_suites(NVersion)], - run_suites(Ciphers, Config, rc4_ecdsa). - -%%------------------------------------------------------------------- -des_rsa_cipher_suites()-> - [{doc, "Test the des_rsa ciphersuites"}]. -des_rsa_cipher_suites(Config) when is_list(Config) -> - NVersion = tls_record:highest_protocol_version([]), - Ciphers = [S || {rsa,_,_} = S <- ssl_test_lib:des_suites(NVersion)], - run_suites(Ciphers, Config, des_rsa). -%------------------------------------------------------------------- -des_ecdh_rsa_cipher_suites()-> - [{doc, "Test ECDH rsa signed ciphersuites"}]. -des_ecdh_rsa_cipher_suites(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = [S || {dhe_rsa,_,_} = S <- ssl_test_lib:des_suites(NVersion)], - run_suites(Ciphers, Config, des_dhe_rsa). - -%%-------------------------------------------------------------------- -default_reject_anonymous()-> - [{doc,"Test that by default anonymous cipher suites are rejected "}]. -default_reject_anonymous(Config) when is_list(Config) -> - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - Version = ssl_test_lib:protocol_version(Config), - TLSVersion = ssl_test_lib:tls_version(Version), - - [CipherSuite | _] = ssl_test_lib:ecdh_dh_anonymous_suites(TLSVersion), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, - [{ciphers,[CipherSuite]} | - ClientOpts]}]), - - ssl_test_lib:check_server_alert(Server, Client, insufficient_security). - -%%-------------------------------------------------------------------- -ciphers_ecdsa_signed_certs() -> - [{doc, "Test all ecdsa ssl cipher suites in highest support ssl/tls version"}]. - -ciphers_ecdsa_signed_certs(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:ecdsa_suites(NVersion), - run_suites(Ciphers, Config, ecdsa). -%%-------------------------------------------------------------------- -ciphers_ecdsa_signed_certs_openssl_names() -> - [{doc, "Test all ecdsa ssl cipher suites in highest support ssl/tls version"}]. - -ciphers_ecdsa_signed_certs_openssl_names(Config) when is_list(Config) -> - Ciphers = ssl_test_lib:openssl_ecdsa_suites(), - run_suites(Ciphers, Config, ecdsa). -%%-------------------------------------------------------------------- -ciphers_ecdh_rsa_signed_certs() -> - [{doc, "Test all ecdh_rsa ssl cipher suites in highest support ssl/tls version"}]. - -ciphers_ecdh_rsa_signed_certs(Config) when is_list(Config) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:ecdh_rsa_suites(NVersion), - run_suites(Ciphers, Config, ecdh_rsa). -%%-------------------------------------------------------------------- -ciphers_ecdh_rsa_signed_certs_openssl_names() -> - [{doc, "Test all ecdh_rsa ssl cipher suites in highest support ssl/tls version"}]. - -ciphers_ecdh_rsa_signed_certs_openssl_names(Config) when is_list(Config) -> - Ciphers = ssl_test_lib:openssl_ecdh_rsa_suites(), - run_suites(Ciphers, Config, ecdh_rsa). -%%-------------------------------------------------------------------- -reuse_session() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -reuse_session(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). -%%-------------------------------------------------------------------- -reuse_session_expired() -> - [{doc,"Test sessions is not reused when it has expired"}]. -reuse_session_expired(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server0 = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {tcp_options, [{active, false}]}, - {options, ServerOpts}]), - Port0 = ssl_test_lib:inet_port(Server0), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port0}, {host, Hostname}, - {mfa, {ssl_test_lib, session_id, []}}, - {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), - Server0 ! listen, - - Client1 = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port0}, {host, Hostname}, - {mfa, {ssl_test_lib, session_id, []}}, - {from, self()}, {options, ClientOpts}]), - - SID = receive - {Client0, Id0} -> - Id0 - end, - - receive - {Client1, SID} -> - ok - after ?SLEEP -> - ct:fail(session_not_reused) - end, - - Server0 ! listen, - - %% Make sure session is unregistered due to expiration - ct:sleep((?EXPIRE*2)), - - make_sure_expired(Hostname, Port0, SID), - - Client2 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port0}, {host, Hostname}, - {mfa, {ssl_test_lib, session_id, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client2, SID} -> - ct:fail(session_reused_when_session_expired); - {Client2, _} -> - ok - end, - process_flag(trap_exit, false), - ssl_test_lib:close(Server0), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1), - ssl_test_lib:close(Client2). - -make_sure_expired(Host, Port, Id) -> - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - ClientCache = element(2, State), - - case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of - undefined -> - ok; - #session{is_resumable = false} -> - ok; - _ -> - ct:sleep(?SLEEP), - make_sure_expired(Host, Port, Id) - end. - -%%-------------------------------------------------------------------- -server_does_not_want_to_reuse_session() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -server_does_not_want_to_reuse_session(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, [{reuse_session, fun(_,_,_,_) -> - false - end} | - ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - ssl_test_lib:close(Client0), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ct:fail(session_reused_when_server_does_not_want_to); - {Client1, _Other} -> - ok - end, - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -client_renegotiate() -> - [{doc,"Test ssl:renegotiate/1 on client."}]. -client_renegotiate(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -client_secure_renegotiate() -> - [{doc,"Test ssl:renegotiate/1 on client."}]. -client_secure_renegotiate(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{secure_renegotiate, true} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, - {options, [{reuse_sessions, false}, - {secure_renegotiate, true}| ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -client_secure_renegotiate_fallback() -> - [{doc,"Test that we can set secure_renegotiate to false that is " - "fallback option, we however do not have a insecure server to test against!"}]. -client_secure_renegotiate_fallback(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{secure_renegotiate, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), + {mfa, {?MODULE, protocol_info_result, []}}, + {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ClientOpts]}]), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, - {options, [{reuse_sessions, false}, - {secure_renegotiate, false}| ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). + ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). + +connect_dist() -> + [{doc,"Test a simple connect as is used by distribution"}]. -%%-------------------------------------------------------------------- -server_renegotiate() -> - [{doc,"Test ssl:renegotiate/1 on server."}]. -server_renegotiate(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), +connect_dist(Config) when is_list(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_kc_opts, Config), + ClientOpts = [{ssl_imp, new},{active, false}, {packet,4}|ClientOpts0], + ServerOpts0 = ssl_test_lib:ssl_options(server_kc_opts, Config), + ServerOpts = [{ssl_imp, new},{active, false}, {packet,4}|ServerOpts0], {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Data = "From erlang to erlang", - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, + {mfa, {?MODULE, connect_dist_s, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -client_renegotiate_reused_session() -> - [{doc,"Test ssl:renegotiate/1 on client when the ssl session will be reused."}]. -client_renegotiate_reused_session(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_reuse_session, [Data]}}, - {options, [{reuse_sessions, true} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -server_renegotiate_reused_session() -> - [{doc,"Test ssl:renegotiate/1 on server when the ssl session will be reused."}]. -server_renegotiate_reused_session(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_reuse_session, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{reuse_sessions, true} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -client_no_wrap_sequence_number() -> - [{doc,"Test that erlang client will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. - -client_no_wrap_sequence_number(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to erlang", - N = 12, - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Version = ssl_test_lib:protocol_version(Config, tuple), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[ErlData, treashold(N, Version)]]}}, - {options, [{reuse_sessions, false}, - {renegotiate_at, N} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -server_no_wrap_sequence_number() -> - [{doc, "Test that erlang server will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. - -server_no_wrap_sequence_number(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - N = 12, - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[Data, N+2]]}}, - {options, [{renegotiate_at, N} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -der_input() -> - [{doc,"Test to input certs and key as der"}]. - -der_input(Config) when is_list(Config) -> - DataDir = proplists:get_value(data_dir, Config), - DHParamFile = filename:join(DataDir, "dHParam.pem"), - - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - [CADb | _] = element(6, State), - - Size = ets:info(CADb, size), - - SeverVerifyOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ServerCert, ServerKey, ServerCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | - SeverVerifyOpts]), - ClientVerifyOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientCert, ClientKey, ClientCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | - ClientVerifyOpts]), - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, - {dh, DHParams}, - {cert, ServerCert}, {key, ServerKey}, {cacerts, ServerCaCerts}], - ClientOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, - {dh, DHParams}, - {cert, ClientCert}, {key, ClientKey}, {cacerts, ClientCaCerts}], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - Size = ets:info(CADb, size). - -%%-------------------------------------------------------------------- -der_input_opts(Opts) -> - Certfile = proplists:get_value(certfile, Opts), - CaCertsfile = proplists:get_value(cacertfile, Opts), - Keyfile = proplists:get_value(keyfile, Opts), - Dhfile = proplists:get_value(dhfile, Opts), - [{_, Cert, _}] = ssl_test_lib:pem_to_der(Certfile), - [{Asn1Type, Key, _}] = ssl_test_lib:pem_to_der(Keyfile), - [{_, DHParams, _}] = ssl_test_lib:pem_to_der(Dhfile), - CaCerts = - lists:map(fun(Entry) -> - {_, CaCert, _} = Entry, - CaCert - end, ssl_test_lib:pem_to_der(CaCertsfile)), - {Cert, {Asn1Type, Key}, CaCerts, DHParams}. - -%%-------------------------------------------------------------------- -no_reuses_session_server_restart_new_cert() -> - [{doc,"Check that a session is not reused if the server is restarted with a new cert."}]. -no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> - - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - %% Make sure session is registered - ct:sleep(?SLEEP), - Monitor = erlang:monitor(process, Server), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client0), - receive - {'DOWN', Monitor, _, _, _} -> - ok - end, - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{reuseaddr, true} | DsaServerOpts]}]), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ct:fail(session_reused_when_server_has_new_cert); - {Client1, _Other} -> - ok - end, - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -no_reuses_session_server_restart_new_cert_file() -> - [{doc,"Check that a session is not reused if a server is restarted with a new " - "cert contained in a file with the same name as the old cert."}]. - -no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), - - NewServerOpts0 = new_config(PrivDir, ServerOpts), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, NewServerOpts0}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - %% Make sure session is registered and we get - %% new file time stamp when calling new_config! - ct:sleep(?SLEEP* 2), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client0), - - ssl:clear_pem_cache(), - - NewServerOpts1 = new_config(PrivDir, DsaServerOpts), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{reuseaddr, true} | NewServerOpts1]}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ct:fail(session_reused_when_server_has_new_cert); - {Client1, _Other} -> - ok - end, - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -defaults(Config) when is_list(Config)-> - Versions = ssl:versions(), - true = lists:member(sslv3, proplists:get_value(available, Versions)), - false = lists:member(sslv3, proplists:get_value(supported, Versions)), - true = lists:member('tlsv1', proplists:get_value(available, Versions)), - true = lists:member('tlsv1', proplists:get_value(supported, Versions)), - true = lists:member('tlsv1.1', proplists:get_value(available, Versions)), - true = lists:member('tlsv1.1', proplists:get_value(supported, Versions)), - true = lists:member('tlsv1.2', proplists:get_value(available, Versions)), - true = lists:member('tlsv1.2', proplists:get_value(supported, Versions)), - false = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites()), - true = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites(all)), - false = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites()), - true = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites(all)), - false = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites()), - true = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites(all)), - true = lists:member('dtlsv1.2', proplists:get_value(available_dtls, Versions)), - true = lists:member('dtlsv1', proplists:get_value(available_dtls, Versions)), - true = lists:member('dtlsv1.2', proplists:get_value(supported_dtls, Versions)), - true = lists:member('dtlsv1', proplists:get_value(supported_dtls, Versions)). - -%%-------------------------------------------------------------------- -reuseaddr() -> - [{doc,"Test reuseaddr option"}]. - -reuseaddr(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false} | ClientOpts]}]), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server1, ok, Client1, ok), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -tls_tcp_reuseaddr() -> - [{doc, "Reference test case."}]. -tls_tcp_reuseaddr(Config) when is_list(Config) -> - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {transport, gen_tcp}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false}, {reuseaddr, true}]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {transport, gen_tcp}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false}]}]), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {transport, gen_tcp}, - {mfa, {?MODULE, tcp_send_recv_result, []}}, - {options, [{active, false}, {reuseaddr, true}]}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {transport, gen_tcp}, - {mfa, {?MODULE, tcp_send_recv_result, []}}, - {options, [{active, false}]}]), - - ssl_test_lib:check_result(Server1, ok, Client1, ok), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- - -honor_server_cipher_order() -> - [{doc,"Test API honor server cipher order."}]. -honor_server_cipher_order(Config) when is_list(Config) -> - ClientCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac => sha, - prf => default_prf}], - ServerCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac =>sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}], - honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac => sha, - prf => default_prf}). - -honor_client_cipher_order() -> - [{doc,"Test API honor server cipher order."}]. -honor_client_cipher_order(Config) when is_list(Config) -> - ClientCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac => sha, - prf => default_prf}], - ServerCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac =>sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}], -honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}). - -honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, connection_info_result, []}}, - {options, [{ciphers, ServerCiphers}, {honor_cipher_order, Honor} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, connection_info_result, []}}, - {options, [{ciphers, ClientCiphers}, {honor_cipher_order, Honor} - | ClientOpts]}]), - - Version = ssl_test_lib:protocol_version(Config), - - ServerMsg = ClientMsg = {ok, {Version, Expected}}, - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_ciphersuite_vs_version() -> - [{doc,"Test a SSLv3 client can not negotiate a TLSv* cipher suite."}]. -tls_ciphersuite_vs_version(Config) when is_list(Config) -> - - {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), - ok = gen_tcp:send(Socket, - <<22, 3,0, 49:16, % handshake, SSL 3.0, length - 1, 45:24, % client_hello, length - 3,0, % SSL 3.0 - 16#deadbeef:256, % 32 'random' bytes = 256 bits - 0, % no session ID - %% three cipher suites -- null, one with sha256 hash and one with sha hash - 6:16, 0,255, 0,61, 0,57, - 1, 0 % no compression - >>), - {ok, <<22, RecMajor:8, RecMinor:8, _RecLen:16, 2, HelloLen:24>>} = gen_tcp:recv(Socket, 9, 10000), - {ok, <<HelloBin:HelloLen/binary>>} = gen_tcp:recv(Socket, HelloLen, 5000), - ServerHello = tls_handshake:decode_handshake({RecMajor, RecMinor}, 2, HelloBin), - case ServerHello of - #server_hello{server_version = {3,0}, cipher_suite = <<0,57>>} -> - ok; - _ -> - ct:fail({unexpected_server_hello, ServerHello}) - end. - -%%-------------------------------------------------------------------- -conf_signature_algs() -> - [{doc,"Test to set the signature_algs option on both client and server"}]. -conf_signature_algs(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -no_common_signature_algs() -> - [{doc,"Set the signature_algs option so that there client and server does not share any hash sign algorithms"}]. -no_common_signature_algs(Config) when is_list(Config) -> - - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [{signature_algs, [{sha256, rsa}]} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{signature_algs, [{sha384, rsa}]} - | ClientOpts]}]), - - ssl_test_lib:check_server_alert(Server, Client, insufficient_security). - -%%-------------------------------------------------------------------- - -tls_dont_crash_on_handshake_garbage() -> - [{doc, "Ensure SSL server worker thows an alert on garbage during handshake " - "instead of crashing and exposing state to user code"}]. - -tls_dont_crash_on_handshake_garbage(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - unlink(Server), monitor(process, Server), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), - - % Send hello and garbage record - ok = gen_tcp:send(Socket, - [<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello - 16#deadbeef:256, % 32 'random' bytes = 256 bits - 0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values - - <<22, 3,3, 5:16, 92,64,37,228,209>> % garbage - ]), - % Send unexpected change_cipher_spec - ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>), - - % Ensure we receive an alert, not sudden disconnect - {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000). - -drop_handshakes(Socket, Timeout) -> - {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout), - {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout), - case RecType of - 22 -> drop_handshakes(Socket, Timeout); - _ -> {ok, <<Header/binary, Frag/binary>>} - end. - - -%%-------------------------------------------------------------------- - -hibernate() -> - [{doc,"Check that an SSL connection that is started with option " - "{hibernate_after, 1000} indeed hibernates after 1000ms of " - "inactivity"}]. - -hibernate(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {Client, #sslsocket{pid=[Pid|_]}} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{hibernate_after, 1000}|ClientOpts]}]), - {current_function, _} = - process_info(Pid, current_function), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - timer:sleep(1500), - {current_function, {erlang, hibernate, 3}} = - process_info(Pid, current_function), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -hibernate_right_away() -> - [{doc,"Check that an SSL connection that is configured to hibernate " - "after 0 or 1 milliseconds hibernates as soon as possible and not " - "crashes"}]. - -hibernate_right_away(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - StartServerOpts = [{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}], - StartClientOpts = [return_socket, - {node, ClientNode}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}], - - Server1 = ssl_test_lib:start_server(StartServerOpts), - Port1 = ssl_test_lib:inet_port(Server1), - {Client1, #sslsocket{pid = [Pid1|_]}} = ssl_test_lib:start_client(StartClientOpts ++ - [{port, Port1}, {options, [{hibernate_after, 0}|ClientOpts]}]), - - ssl_test_lib:check_result(Server1, ok, Client1, ok), - - {current_function, {erlang, hibernate, 3}} = - process_info(Pid1, current_function), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1), - - Server2 = ssl_test_lib:start_server(StartServerOpts), - Port2 = ssl_test_lib:inet_port(Server2), - {Client2, #sslsocket{pid = [Pid2|_]}} = ssl_test_lib:start_client(StartClientOpts ++ - [{port, Port2}, {options, [{hibernate_after, 1}|ClientOpts]}]), - - ssl_test_lib:check_result(Server2, ok, Client2, ok), - - ct:sleep(1000), %% Schedule out - - {current_function, {erlang, hibernate, 3}} = - process_info(Pid2, current_function), - - ssl_test_lib:close(Server2), - ssl_test_lib:close(Client2). - -%%-------------------------------------------------------------------- -listen_socket() -> - [{doc,"Check error handling and inet compliance when calling API functions with listen sockets."}]. - -listen_socket(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ok, ListenSocket} = ssl:listen(0, ServerOpts), - - %% This can be a valid thing to do as - %% options are inherited by the accept socket - ok = ssl:controlling_process(ListenSocket, self()), - - {ok, _} = ssl:sockname(ListenSocket), - - {error, enotconn} = ssl:send(ListenSocket, <<"data">>), - {error, enotconn} = ssl:recv(ListenSocket, 0), - {error, enotconn} = ssl:connection_information(ListenSocket), - {error, enotconn} = ssl:peername(ListenSocket), - {error, enotconn} = ssl:peercert(ListenSocket), - {error, enotconn} = ssl:renegotiate(ListenSocket), - {error, enotconn} = ssl:prf(ListenSocket, 'master_secret', <<"Label">>, [client_random], 256), - {error, enotconn} = ssl:shutdown(ListenSocket, read_write), - - ok = ssl:close(ListenSocket). -%%-------------------------------------------------------------------- -tls_ssl_accept_timeout() -> - [{doc,"Test ssl:ssl_accept timeout"}]. - -tls_ssl_accept_timeout(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {ssl_test_lib, - no_result_msg, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {ok, CSocket} = gen_tcp:connect(Hostname, Port, [binary, {active, true}]), - - receive - {tcp_closed, CSocket} -> - ssl_test_lib:check_result(Server, {error, timeout}), - receive - {'EXIT', Server, _} -> - %% Make sure supervisor had time to react on process exit - %% Could we come up with a better solution to this? - ct:sleep(500), - [] = supervisor:which_children(tls_connection_sup) - end - end. - -%%-------------------------------------------------------------------- -ssl_recv_timeout() -> - [{doc,"Test ssl:ssl_accept timeout"}]. - -ssl_recv_timeout(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, send_recv_result_timeout_server, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - send_recv_result_timeout_client, []}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -connect_twice() -> - [{doc,""}]. -connect_twice(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{keepalive, true},{active, false} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{keepalive, true},{active, false} - | ClientOpts]}]), - Server ! listen, - - {Client1, #sslsocket{}} = - ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{keepalive, true},{active, false} - | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:check_result(Server, ok, Client1, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -renegotiate_dos_mitigate_active() -> - [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", - "immediately after each other"}]. -renegotiate_dos_mitigate_active(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_immediately, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -renegotiate_dos_mitigate_passive() -> - [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", - "immediately after each other"}]. -renegotiate_dos_mitigate_passive(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, {from, self()}, - {mfa, {?MODULE, - renegotiate_immediately, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -renegotiate_dos_mitigate_absolute() -> - [{doc, "Mitigate DOS computational attack by not allowing client to initiate renegotiation"}]. -renegotiate_dos_mitigate_absolute(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{client_renegotiation, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_rejected, - []}}, + {mfa, {?MODULE, connect_dist_c, []}}, {options, ClientOpts}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_tcp_error_propagation_in_active_mode() -> - [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}]. -tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {Client, #sslsocket{pid=[Pid|_]} = SslSocket} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, receive_msg, []}}, - {options, ClientOpts}]), - {status, _, _, StatusInfo} = sys:get_status(Pid), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - StaticEnv = element(2, State), - Socket = element(11, StaticEnv), - %% Fake tcp error - Pid ! {tcp_error, Socket, etimedout}, - - ssl_test_lib:check_result(Client, {ssl_closed, SslSocket}). - -%%-------------------------------------------------------------------- -recv_error_handling() -> - [{doc,"Special case of call error handling"}]. -recv_error_handling(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, recv_close, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - {_Client, #sslsocket{} = SslSocket} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl:close(SslSocket), - ssl_test_lib:check_result(Server, ok). - - - -%%-------------------------------------------------------------------- -call_in_error_state() -> - [{doc,"Special case of call error handling"}]. -call_in_error_state(Config) when is_list(Config) -> - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], - Pid = spawn_link(?MODULE, run_error_server, [[self() | ServerOpts]]), - receive - {Pid, Port} -> - spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) - end, - receive - {error, closed} -> - ok; - Other -> - ct:fail(Other) - end. - -run_client_error([Port, Opts]) -> - ssl:connect("localhost", Port, Opts). - -run_error_server([ Pid | Opts]) -> - {ok, Listen} = ssl:listen(0, Opts), - {ok,{_, Port}} = ssl:sockname(Listen), - Pid ! {self(), Port}, - {ok, Socket} = ssl:transport_accept(Listen), - Pid ! ssl:controlling_process(Socket, self()). - -%%-------------------------------------------------------------------- - -close_in_error_state() -> - [{doc,"Special case of closing socket in error state"}]. -close_in_error_state(Config) when is_list(Config) -> - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), - ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - _ = spawn_link(?MODULE, run_error_server_close, [[self() | ServerOpts]]), - receive - {_Pid, Port} -> - spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) - end, - receive - ok -> - ok; - Other -> - ct:fail(Other) - end. -%%-------------------------------------------------------------------- -abuse_transport_accept_socket() -> - [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. -abuse_transport_accept_socket(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ssl_test_lib:check_result(Server, ok, Client, ok), - Server = ssl_test_lib:start_server_transport_abuse_socket([{node, ServerNode}, - {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -controlling_process_transport_accept_socket() -> - [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. -controlling_process_transport_accept_socket(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server_transport_control([{node, ServerNode}, - {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - _Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server). - -%%-------------------------------------------------------------------- -run_error_server_close([Pid | Opts]) -> - {ok, Listen} = ssl:listen(0, Opts), - {ok,{_, Port}} = ssl:sockname(Listen), - Pid ! {self(), Port}, - {ok, Socket} = ssl:transport_accept(Listen), - Pid ! ssl:close(Socket). - -%%-------------------------------------------------------------------- - -rizzo() -> - [{doc, "Test that there is a 1/n-1-split for non RC4 in 'TLS < 1.1' as it is - vunrable to Rizzo/Dungon attack"}]. - -rizzo(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, NVersion), - [{key_exchange, - fun(Alg) when Alg == ecdh_rsa; Alg == ecdhe_rsa-> - true; - (_) -> - false - end}, - {cipher, - fun(rc4_128) -> - false; - (chacha20_poly1305) -> - false; - (_) -> - true - end}]), - - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_rizzo, []}). -%%-------------------------------------------------------------------- -no_rizzo_rc4() -> - [{doc,"Test that there is no 1/n-1-split for RC4 as it is not vunrable to Rizzo/Dungon attack"}]. - -no_rizzo_rc4(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - %% Test uses RSA certs - Ciphers = ssl:filter_cipher_suites(ssl_test_lib:rc4_suites(NVersion), - [{key_exchange, - fun(Alg) when Alg == ecdh_rsa; Alg == ecdhe_rsa-> - true; - (_) -> - false - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_no_rizzo, []}). - -rizzo_one_n_minus_one() -> - [{doc,"Test that the 1/n-1-split mitigation of Rizzo/Dungon attack can be explicitly selected"}]. - -rizzo_one_n_minus_one(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, NVersion), - [{key_exchange, - fun(Alg) when Alg == ecdh_rsa; Alg == ecdhe_rsa-> - true; - (_) -> - false - end}, - {cipher, - fun(rc4_128) -> - false; - %% TODO: remove this clause when chacha is fixed! - (chacha20_poly1305) -> - false; - (_) -> - true - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_rizzo, []}). - -rizzo_zero_n() -> - [{doc,"Test that the 0/n-split mitigation of Rizzo/Dungon attack can be explicitly selected"}]. - -rizzo_zero_n(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, NVersion), - [{cipher, - fun(rc4_128) -> - false; - (_) -> - true - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_no_rizzo, []}). - -rizzo_disabled() -> - [{doc,"Test that the mitigation of Rizzo/Dungon attack can be explicitly disabled"}]. - -rizzo_disabled(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, NVersion), - [{cipher, - fun(rc4_128) -> - false; - (_) -> - true - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_no_rizzo, []}). - -%%-------------------------------------------------------------------- -new_server_wants_peer_cert() -> - [{doc, "Test that server configured to do client certification does" - " not reuse session without a client certificate."}]. -new_server_wants_peer_cert(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - VServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, [ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - Monitor = erlang:monitor(process, Server), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - receive - {'DOWN', Monitor, _, _, _} -> - ok - end, - - Server1 = ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, VServerOpts}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [ClientOpts]}]), - - CertFile = proplists:get_value(certfile, ClientOpts), - [{'Certificate', BinCert, _}]= ssl_test_lib:pem_to_der(CertFile), - - ServerMsg = {error, no_peercert}, - Sever1Msg = {ok, BinCert}, - - ssl_test_lib:check_result(Server, ServerMsg, Server1, Sever1Msg), - - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -session_cache_process_list() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -session_cache_process_list(Config) when is_list(Config) -> - session_cache_process(list,Config). -%%-------------------------------------------------------------------- -session_cache_process_mnesia() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -session_cache_process_mnesia(Config) when is_list(Config) -> - session_cache_process(mnesia,Config). +eccs() -> + [{doc, "Test API functions eccs/0 and eccs/1"}]. -%%-------------------------------------------------------------------- +eccs(Config) when is_list(Config) -> + [_|_] = All = ssl:eccs(), + [] = ssl:eccs(sslv3), + [_|_] = Tls = ssl:eccs(tlsv1), + [_|_] = Tls1 = ssl:eccs('tlsv1.1'), + [_|_] = Tls2 = ssl:eccs('tlsv1.2'), + [_|_] = Tls1 = ssl:eccs('dtlsv1'), + [_|_] = Tls2 = ssl:eccs('dtlsv1.2'), + %% ordering is currently not verified by the test + true = lists:sort(All) =:= lists:usort(Tls ++ Tls1 ++ Tls2), + ok. tls_versions_option() -> [{doc,"Test API versions option to connect/listen."}]. @@ -4402,165 +452,6 @@ tls_versions_option(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -unordered_protocol_versions_server() -> - [{doc,"Test that the highest protocol is selected even" - " when it is not first in the versions list."}]. - -unordered_protocol_versions_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, ClientOpts}]), - - ServerMsg = ClientMsg = {ok,'tlsv1.2'}, - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). - -%%-------------------------------------------------------------------- -unordered_protocol_versions_client() -> - [{doc,"Test that the highest protocol is selected even" - " when it is not first in the versions list."}]. - -unordered_protocol_versions_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, ServerOpts }]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ClientOpts]}]), - - ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). - -%%-------------------------------------------------------------------- -max_handshake_size() -> - [{doc,"Test that we can set max_handshake_size to max value."}]. - -max_handshake_size(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{max_handshake_size, 8388607} |ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{max_handshake_size, 8388607} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok). - -%%-------------------------------------------------------------------- - -server_name_indication_option() -> - [{doc,"Test API server_name_indication option to connect."}]. -server_name_indication_option(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{server_name_indication, disable} | - ClientOpts]} - ]), - - ssl_test_lib:check_result(Server, ok, Client0, ok), - Server ! listen, - - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{server_name_indication, Hostname} | ClientOpts] - }]), - ssl_test_lib:check_result(Server, ok, Client1, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1). -%%-------------------------------------------------------------------- - -accept_pool() -> - [{doc,"Test having an accept pool."}]. -accept_pool(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server0 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {accepters, 3}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server0), - [Server1, Server2] = ssl_test_lib:accepters(2), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts} - ]), - - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts} - ]), - - Client2 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts} - ]), - - ssl_test_lib:check_ok([Server0, Server1, Server2, Client0, Client1, Client2]), - - ssl_test_lib:close(Server0), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Server2), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1), - ssl_test_lib:close(Client2). - - -%%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- send_recv_result(Socket) -> @@ -4572,632 +463,9 @@ tcp_send_recv_result(Socket) -> {ok,"Hello world"} = gen_tcp:recv(Socket, 11), ok. -basic_verify_test_no_close(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - {Server, Client}. - -basic_test(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -prf_create_plan(TlsVersions, PRFs, Results) -> - lists:foldl(fun(Ver, Acc) -> - A = prf_ciphers_and_expected(Ver, PRFs, Results), - [A|Acc] - end, [], TlsVersions). -prf_ciphers_and_expected(TlsVer, PRFs, Results) -> - case TlsVer of - TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1 - orelse TlsVer == 'tlsv1.1' orelse TlsVer == 'dtlsv1' -> - Ciphers = ssl:cipher_suites(), - {_, Expected} = lists:keyfind(md5sha, 1, Results), - [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]]; - TlsVer when TlsVer == 'tlsv1.2' orelse TlsVer == 'dtlsv1.2'-> - lists:foldl( - fun(PRF, Acc) -> - Ciphers = prf_get_ciphers(TlsVer, PRF), - case Ciphers of - [] -> - ct:log("No ciphers for PRF algorithm ~p. Skipping.", [PRF]), - Acc; - Ciphers -> - {_, Expected} = lists:keyfind(PRF, 1, Results), - [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, - {prf, PRF}] | Acc] - end - end, [], PRFs) - end. -prf_get_ciphers(_, PRF) -> - lists:filter( - fun(C) when tuple_size(C) == 4 andalso - element(4, C) == PRF -> - true; - (_) -> - false - end, - ssl:cipher_suites()). -prf_run_test(_, TlsVer, [], _, Prf) -> - ct:fail({error, cipher_list_empty, TlsVer, Prf}); -prf_run_test(Config, TlsVer, Ciphers, Expected, Prf) -> - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - BaseOpts = [{active, true}, {versions, [TlsVer]}, {ciphers, Ciphers}, {protocol, tls_or_dtls(TlsVer)}], - ServerOpts = BaseOpts ++ proplists:get_value(server_opts, Config), - ClientOpts = BaseOpts ++ proplists:get_value(client_opts, Config), - Server = ssl_test_lib:start_server( - [{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client( - [{node, ClientNode}, {port, Port}, - {host, Hostname}, {from, self()}, - {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -prf_verify_value(Socket, TlsVer, Expected, Algo) -> - Ret = ssl:prf(Socket, <<>>, <<>>, [<<>>], 16), - case TlsVer of - sslv3 -> - case Ret of - {error, undefined} -> ok; - _ -> - {error, {expected, {error, undefined}, - got, Ret, tls_ver, TlsVer, prf_algorithm, Algo}} - end; - _ -> - case Ret of - {ok, Expected} -> ok; - {ok, Val} -> {error, {expected, Expected, got, Val, tls_ver, TlsVer, - prf_algorithm, Algo}} - end - end. - -send_recv_result_timeout_client(Socket) -> - {error, timeout} = ssl:recv(Socket, 11, 500), - {error, timeout} = ssl:recv(Socket, 11, 0), - ssl:send(Socket, "Hello world"), - receive - Msg -> - io:format("Msg ~p~n",[Msg]) - after 500 -> - ok - end, - {ok, "Hello world"} = ssl:recv(Socket, 11, 500), - ok. -send_recv_result_timeout_server(Socket) -> - ssl:send(Socket, "Hello"), - {ok, "Hello world"} = ssl:recv(Socket, 11), - ssl:send(Socket, " world"), - ok. - -recv_close(Socket) -> - {error, closed} = ssl:recv(Socket, 11), - receive - {_,{error,closed}} -> - error_extra_close_sent_to_user_process - after 500 -> - ok - end. - - -send_recv_result_active_rizzo(Socket) -> - ssl:send(Socket, "Hello world"), - "Hello world" = ssl_test_lib:active_recv(Socket, 11), - ok. - -send_recv_result_active_no_rizzo(Socket) -> - ssl:send(Socket, "Hello world"), - "Hello world" = ssl_test_lib:active_recv(Socket, 11), - ok. - - -ssl_active_recv(N) -> - ssl_active_recv(N, []). - -ssl_active_recv(0, Acc) -> - Acc; -ssl_active_recv(N, Acc) -> - receive - {ssl, _, Bytes} -> - ssl_active_recv(N-length(Bytes), Acc ++ Bytes) - end. - result_ok(_Socket) -> ok. -renegotiate(Socket, Data) -> - ct:log("Renegotiating ~n", []), - Result = ssl:renegotiate(Socket), - ct:log("Result ~p~n", [Result]), - ssl:send(Socket, Data), - case Result of - ok -> - ok; - Other -> - Other - end. - -renegotiate_reuse_session(Socket, Data) -> - %% Make sure session is registered - ct:sleep(?SLEEP), - renegotiate(Socket, Data). - -renegotiate_immediately(Socket) -> - _ = ssl_test_lib:active_recv(Socket, 11), - ok = ssl:renegotiate(Socket), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:sleep(?RENEGOTIATION_DISABLE_TIME + ?SLEEP), - ok = ssl:renegotiate(Socket), - ct:log("Renegotiated again"), - ssl:send(Socket, "Hello world"), - ok. - -renegotiate_rejected(Socket) -> - _ = ssl_test_lib:active_recv(Socket, 11), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:log("Failed to renegotiate again"), - ssl:send(Socket, "Hello world"), - ok. - -rizzo_add_mitigation_option(Value, Config) -> - lists:foldl(fun(Opt, Acc) -> - case proplists:get_value(Opt, Acc) of - undefined -> Acc; - C -> - N = lists:keystore(beast_mitigation, 1, C, - {beast_mitigation, Value}), - lists:keystore(Opt, 1, Acc, {Opt, N}) - end - end, Config, - [client_opts, client_dsa_opts, server_opts, server_dsa_opts, - server_ecdsa_opts, server_ecdh_rsa_opts]). - -new_config(PrivDir, ServerOpts0) -> - CaCertFile = proplists:get_value(cacertfile, ServerOpts0), - CertFile = proplists:get_value(certfile, ServerOpts0), - KeyFile = proplists:get_value(keyfile, ServerOpts0), - NewCaCertFile = filename:join(PrivDir, "new_ca.pem"), - NewCertFile = filename:join(PrivDir, "new_cert.pem"), - NewKeyFile = filename:join(PrivDir, "new_key.pem"), - file:copy(CaCertFile, NewCaCertFile), - file:copy(CertFile, NewCertFile), - file:copy(KeyFile, NewKeyFile), - ServerOpts1 = proplists:delete(cacertfile, ServerOpts0), - ServerOpts2 = proplists:delete(certfile, ServerOpts1), - ServerOpts = proplists:delete(keyfile, ServerOpts2), - - {ok, PEM} = file:read_file(NewCaCertFile), - ct:log("CA file content: ~p~n", [public_key:pem_decode(PEM)]), - - [{cacertfile, NewCaCertFile}, {certfile, NewCertFile}, - {keyfile, NewKeyFile} | ServerOpts]. - -session_cache_process(_Type,Config) when is_list(Config) -> - reuse_session(Config). - -init([Type]) -> - ets:new(ssl_test, [named_table, public, set]), - ets:insert(ssl_test, {type, Type}), - case Type of - list -> - spawn(fun() -> session_loop([]) end); - mnesia -> - mnesia:start(), - {atomic,ok} = mnesia:create_table(sess_cache, []), - sess_cache - end. - -session_cb() -> - [{type, Type}] = ets:lookup(ssl_test, type), - Type. - -terminate(Cache) -> - case session_cb() of - list -> - Cache ! terminate; - mnesia -> - catch {atomic,ok} = - mnesia:delete_table(sess_cache) - end. - -lookup(Cache, Key) -> - case session_cb() of - list -> - Cache ! {self(), lookup, Key}, - receive {Cache, Res} -> Res end; - mnesia -> - case mnesia:transaction(fun() -> - mnesia:read(sess_cache, - Key, read) - end) of - {atomic, [{sess_cache, Key, Value}]} -> - Value; - _ -> - undefined - end - end. - -update(Cache, Key, Value) -> - case session_cb() of - list -> - Cache ! {update, Key, Value}; - mnesia -> - {atomic, ok} = - mnesia:transaction(fun() -> - mnesia:write(sess_cache, - {sess_cache, Key, Value}, write) - end) - end. - -delete(Cache, Key) -> - case session_cb() of - list -> - Cache ! {delete, Key}; - mnesia -> - {atomic, ok} = - mnesia:transaction(fun() -> - mnesia:delete(sess_cache, Key) - end) - end. - -foldl(Fun, Acc, Cache) -> - case session_cb() of - list -> - Cache ! {self(),foldl,Fun,Acc}, - receive {Cache, Res} -> Res end; - mnesia -> - Foldl = fun() -> - mnesia:foldl(Fun, Acc, sess_cache) - end, - {atomic, Res} = mnesia:transaction(Foldl), - Res - end. - -select_session(Cache, PartialKey) -> - case session_cb() of - list -> - Cache ! {self(),select_session, PartialKey}, - receive - {Cache, Res} -> - Res - end; - mnesia -> - Sel = fun() -> - mnesia:select(Cache, - [{{sess_cache,{PartialKey,'$1'}, '$2'}, - [],['$$']}]) - end, - {atomic, Res} = mnesia:transaction(Sel), - Res - end. - -session_loop(Sess) -> - receive - terminate -> - ok; - {Pid, lookup, Key} -> - case lists:keysearch(Key,1,Sess) of - {value, {Key,Value}} -> - Pid ! {self(), Value}; - _ -> - Pid ! {self(), undefined} - end, - session_loop(Sess); - {update, Key, Value} -> - NewSess = [{Key,Value}| lists:keydelete(Key,1,Sess)], - session_loop(NewSess); - {delete, Key} -> - session_loop(lists:keydelete(Key,1,Sess)); - {Pid,foldl,Fun,Acc} -> - Res = lists:foldl(Fun, Acc,Sess), - Pid ! {self(), Res}, - session_loop(Sess); - {Pid,select_session,PKey} -> - Sel = fun({{PKey0, Id},Session}, Acc) when PKey == PKey0 -> - [[Id, Session]|Acc]; - (_,Acc) -> - Acc - end, - Sessions = lists:foldl(Sel, [], Sess), - Pid ! {self(), Sessions}, - session_loop(Sess) - end. - - -erlang_ssl_receive(Socket, Data) -> - case ssl_test_lib:active_recv(Socket, length(Data)) of - Data -> - ok; - Other -> - ct:fail({{expected, Data}, {got, Other}}) - end. - -receive_msg(_) -> - receive - Msg -> - Msg - end. - -controlling_process_result(Socket, Pid, Msg) -> - ok = ssl:controlling_process(Socket, Pid), - %% Make sure other side has evaluated controlling_process - %% before message is sent - ct:sleep(?SLEEP), - ssl:send(Socket, Msg), - no_result_msg. - - -controller_dies_result(_Socket, _Pid, _Msg) -> - receive Result -> Result end. - -get_close(Pid, Where) -> - receive - {'EXIT', Pid, _Reason} -> - receive - {_, {ssl_closed, Socket}} -> - ct:log("Socket closed ~p~n",[Socket]); - Unexpected -> - ct:log("Unexpected ~p~n",[Unexpected]), - ct:fail({line, ?LINE-1}) - after 5000 -> - ct:fail({timeout, {line, ?LINE, Where}}) - end; - Unexpected -> - ct:log("Unexpected ~p~n",[Unexpected]), - ct:fail({line, ?LINE-1}) - after 5000 -> - ct:fail({timeout, {line, ?LINE, Where}}) - end. - -run_send_recv_rizzo(Ciphers, Config, Version, Mfa) -> - Result = lists:map(fun(Cipher) -> - rizzo_test(Cipher, Config, Version, Mfa) end, - Ciphers), - case lists:flatten(Result) of - [] -> - ok; - Error -> - ct:log("Cipher suite errors: ~p~n", [Error]), - ct:fail(cipher_suite_failed_see_test_case_log) - end. - -rizzo_test(Cipher, Config, Version, Mfa) -> - {ClientOpts, ServerOpts} = client_server_opts(Cipher, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, Mfa}, - {options, [{active, true}, {ciphers, [Cipher]}, - {versions, [Version]} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, Mfa}, - {options, [{active, true}, {ciphers, [Cipher]}| ClientOpts]}]), - - Result = ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - case Result of - ok -> - []; - Error -> - [{Cipher, Error}] - end. - -client_server_opts(#{key_exchange := KeyAlgo}, Config) - when KeyAlgo == rsa orelse - KeyAlgo == dhe_rsa orelse - KeyAlgo == ecdhe_rsa orelse - KeyAlgo == rsa_psk orelse - KeyAlgo == srp_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_opts, Config)}; -client_server_opts(#{key_exchange := KeyAlgo}, Config) when KeyAlgo == dss orelse KeyAlgo == dhe_dss -> - {ssl_test_lib:ssl_options(client_dsa_opts, Config), - ssl_test_lib:ssl_options(server_dsa_opts, Config)}; -client_server_opts(#{key_exchange := KeyAlgo}, Config) when KeyAlgo == ecdh_ecdsa orelse KeyAlgo == ecdhe_ecdsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_ecdsa_opts, Config)}; -client_server_opts(#{key_exchange := KeyAlgo}, Config) when KeyAlgo == ecdh_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}. - -run_suites(Ciphers, Config, Type) -> - Version = ssl_test_lib:protocol_version(Config), - ct:log("Running cipher suites ~p~n", [Ciphers]), - {ClientOpts, ServerOpts} = - case Type of - rsa -> - {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_rsa_opts, Config)]}; - dsa -> - {ssl_test_lib:ssl_options(client_dsa_verify_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_dsa_opts, Config)]}; - anonymous -> - %% No certs in opts! - {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options([], Config)]}; - psk -> - {ssl_test_lib:ssl_options(client_psk, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_psk, Config)]}; - psk_with_hint -> - {ssl_test_lib:ssl_options(client_psk, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_psk_hint, Config) - ]}; - psk_anon -> - {ssl_test_lib:ssl_options(client_psk, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_psk_anon, Config)]}; - psk_anon_with_hint -> - {ssl_test_lib:ssl_options(client_psk, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_psk_anon_hint, Config)]}; - srp -> - {ssl_test_lib:ssl_options(client_srp, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_srp, Config)]}; - srp_anon -> - {ssl_test_lib:ssl_options(client_srp, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_srp_anon, Config)]}; - srp_dsa -> - {ssl_test_lib:ssl_options(client_srp_dsa, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_srp_dsa, Config)]}; - ecdsa -> - {ssl_test_lib:ssl_options(client_ecdsa_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}; - ecdh_rsa -> - {ssl_test_lib:ssl_options(client_ecdh_rsa_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]}; - rc4_rsa -> - {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]}; - rc4_ecdh_rsa -> - {ssl_test_lib:ssl_options(client_ecdh_rsa_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]}; - rc4_ecdsa -> - {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}; - des_dhe_rsa -> - {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_verification_opts, Config)]}; - des_rsa -> - {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]}; - chacha_rsa -> - {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]}; - chacha_ecdsa -> - {ssl_test_lib:ssl_options(client_ecdsa_opts, Config), - [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]} - end, - Suites = ssl_test_lib:filter_suites(Ciphers, Version), - ct:pal("ssl_test_lib:filter_suites(~p ~p) -> ~p ", [Ciphers, Version, Suites]), - Results0 = lists:map(fun(Cipher) -> - cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end, - ssl_test_lib:filter_suites(Ciphers, Version)), - Results = lists:flatten(Results0), - true = length(Results) == length(Suites), - check_cipher_result(Results). - -check_cipher_result([]) -> - ok; -check_cipher_result([ok | Rest]) -> - check_cipher_result(Rest); -check_cipher_result([_ |_] = Error) -> - ct:fail(Error). - -erlang_cipher_suite(Suite) when is_list(Suite)-> - ssl_cipher_format:suite_definition(ssl_cipher_format:openssl_suite(Suite)); -erlang_cipher_suite(Suite) -> - Suite. - -cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> - %% process_flag(trap_exit, true), - ct:log("Testing CipherSuite ~p~n", [CipherSuite]), - ct:log("Server Opts ~p~n", [ServerOpts]), - ct:log("Client Opts ~p~n", [ClientOpts]), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - ErlangCipherSuite = erlang_cipher_suite(CipherSuite), - - ConnectionInfo = {ok, {Version, ErlangCipherSuite}}, - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, cipher_result, [ConnectionInfo]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, cipher_result, [ConnectionInfo]}}, - {options, - [{ciphers,[CipherSuite]} | - ClientOpts]}]), - - Result = ssl_test_lib:wait_for_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - - case Result of - ok -> - [ok]; - Error -> - [{ErlangCipherSuite, Error}] - end. - -connection_information_result(Socket) -> - {ok, Info = [_ | _]} = ssl:connection_information(Socket), - case length(Info) > 3 of - true -> - %% Atleast one ssl_option() is set - ct:log("Info ~p", [Info]), - ok; - false -> - ct:fail(no_ssl_options_returned) - end. - -connection_info_result(Socket) -> - {ok, Info} = ssl:connection_information(Socket, [protocol, selected_cipher_suite]), - {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_cipher_suite, Info)}}. protocol_info_result(Socket) -> {ok, [{protocol, PVersion}]} = ssl:connection_information(Socket, [protocol]), @@ -5207,11 +475,7 @@ version_info_result(Socket) -> {ok, [{version, Version}]} = ssl:connection_information(Socket, [version]), {ok, Version}. -secret_connection_info_result(Socket) -> - {ok, [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]} - = ssl:connection_information(Socket, [client_random, server_random, master_secret]), - is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret). - + connect_dist_s(S) -> Msg = term_to_binary({erlang,term}), ok = ssl:send(S, Msg). @@ -5221,88 +485,11 @@ connect_dist_c(S) -> {ok, Test} = ssl:recv(S, 0, 10000), ok. -tls_downgrade_result(Socket, Pid) -> - ok = ssl_test_lib:send_recv_result(Socket), - Pid ! {self(), ready}, - receive - go -> - ok - end, - case ssl:close(Socket, {self(), 10000}) of - {ok, TCPSocket} -> - inet:setopts(TCPSocket, [{active, true}]), - gen_tcp:send(TCPSocket, "Downgraded"), - receive - {tcp, TCPSocket, <<"Downgraded">>} -> - ok; - {tcp_closed, TCPSocket} -> - ct:fail("Peer timed out, downgrade aborted"), - ok; - Other -> - {error, Other} - end; - {error, timeout} -> - ct:fail("Timed out, downgrade aborted"), - ok; - Fail -> - {error, Fail} - end. - -tls_close(Socket) -> - ok = ssl_test_lib:send_recv_result(Socket), - case ssl:close(Socket, 5000) of - ok -> - ok; - {error, closed} -> - ok; - Other -> - ct:fail(Other) - end. - - %% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast -treashold(N, {3,0}) -> - (N div 2) + 1; -treashold(N, {3,1}) -> - (N div 2) + 1; -treashold(N, _) -> - N + 1. - -get_invalid_inet_option(Socket) -> - {error, {options, {socket_options, foo, _}}} = ssl:getopts(Socket, [foo]), - ok. - -tls_shutdown_result(Socket, server) -> - ssl:send(Socket, "Hej"), - ok = ssl:shutdown(Socket, write), - {ok, "Hej hopp"} = ssl:recv(Socket, 8), - ok; - -tls_shutdown_result(Socket, client) -> - ssl:send(Socket, "Hej hopp"), - ok = ssl:shutdown(Socket, write), - {ok, "Hej"} = ssl:recv(Socket, 3), - ok. - -tls_shutdown_write_result(Socket, server) -> - ct:sleep(?SLEEP), - ssl:shutdown(Socket, write); -tls_shutdown_write_result(Socket, client) -> - ssl:recv(Socket, 0). - dummy(_Socket) -> %% Should not happen as the ssl connection will not be established %% due to fatal handshake failiure exit(kill). -tls_shutdown_both_result(Socket, server) -> - ct:sleep(?SLEEP), - ssl:shutdown(Socket, read_write); -tls_shutdown_both_result(Socket, client) -> - ssl:recv(Socket, 0). - -peername_result(S) -> - ssl:peername(S). - version_option_test(Config, Version) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), @@ -5328,22 +515,3 @@ version_option_test(Config, Version) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). -try_recv_active(Socket) -> - ssl:send(Socket, "Hello world"), - {error, einval} = ssl:recv(Socket, 11), - ok. -try_recv_active_once(Socket) -> - {error, einval} = ssl:recv(Socket, 11), - ok. - - -wait_for_send(Socket) -> - %% Make sure TLS process processed send message event - _ = ssl:connection_information(Socket). - -tls_or_dtls('dtlsv1') -> - dtls; -tls_or_dtls('dtlsv1.2') -> - dtls; -tls_or_dtls(_) -> - tls. diff --git a/lib/ssl/test/ssl_bench.spec b/lib/ssl/test/ssl_bench.spec index 8b746c5ca9..217cc6fc83 100644 --- a/lib/ssl/test/ssl_bench.spec +++ b/lib/ssl/test/ssl_bench.spec @@ -1 +1,2 @@ {suites,"../ssl_test",[ssl_bench_SUITE, ssl_dist_bench_SUITE]}. +{skip_groups,"../ssl_test",ssl_bench_SUITE,basic,"Benchmarks run separately"}. diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl index 13097b08b6..a297539c36 100644 --- a/lib/ssl/test/ssl_bench_SUITE.erl +++ b/lib/ssl/test/ssl_bench_SUITE.erl @@ -25,10 +25,11 @@ suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}]. -all() -> [{group, setup}, {group, payload}, {group, pem_cache}]. +all() -> [{group, basic}, {group, setup}, {group, payload}, {group, pem_cache}]. groups() -> - [{setup, [{repeat, 3}], [setup_sequential, setup_concurrent]}, + [{basic, [], [basic_pem_cache]}, + {setup, [{repeat, 3}], [setup_sequential, setup_concurrent]}, {payload, [{repeat, 3}], [payload_simple]}, {pem_cache, [{repeat, 3}], [use_pem_cache, bypass_pem_cache]} ]. @@ -40,6 +41,7 @@ end_per_group(_GroupName, _Config) -> ok. init_per_suite(Config) -> + ct:timetrap({minutes, 1}), case node() of nonode@nohost -> {skipped, "Node not distributed"}; @@ -51,29 +53,21 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. -init_per_testcase(use_pem_cache, Conf) -> +init_per_testcase(TC, Conf) when TC =:= use_pem_cache; + TC =:= bypass_pem_cache; + TC =:= basic_pem_cache -> case bypass_pem_cache_supported() of false -> {skipped, "PEM cache bypass support required"}; true -> application:set_env(ssl, bypass_pem_cache, false), Conf end; -init_per_testcase(bypass_pem_cache, Conf) -> - case bypass_pem_cache_supported() of - false -> {skipped, "PEM cache bypass support required"}; - true -> - application:set_env(ssl, bypass_pem_cache, true), - Conf - end; init_per_testcase(_Func, Conf) -> Conf. -end_per_testcase(use_pem_cache, _Config) -> - case bypass_pem_cache_supported() of - false -> ok; - true -> application:set_env(ssl, bypass_pem_cache, false) - end; -end_per_testcase(bypass_pem_cache, _Config) -> +end_per_testcase(TC, _Config) when TC =:= use_pem_cache; + TC =:= bypass_pem_cache; + TC =:= basic_pem_cache -> case bypass_pem_cache_supported() of false -> ok; true -> application:set_env(ssl, bypass_pem_cache, false) @@ -119,6 +113,9 @@ payload_simple(Config) -> {suite, "ssl"}, {name, "Payload simple"}]}), ok. +basic_pem_cache(_Config) -> + do_test(ssl, pem_cache, 10, 5, node()). + use_pem_cache(_Config) -> {ok, Result} = do_test(ssl, pem_cache, 100, 500, node()), ct_event:notify(#event{name = benchmark_data, @@ -167,7 +164,7 @@ do_test(Type, TC, Loop, ParallellConnections, Server) -> end end, Spawn = fun(Id) -> - Pid = spawn(fun() -> Test(Id) end), + Pid = spawn_link(fun() -> Test(Id) end), receive {Pid, init} -> Pid end end, Pids = [Spawn(Id) || Id <- lists:seq(ParallellConnections, 1, -1)], @@ -177,49 +174,54 @@ do_test(Type, TC, Loop, ParallellConnections, Server) -> end, {TimeInMicro, _} = timer:tc(Run), TotalTests = ParallellConnections * Loop, - TestPerSecond = 1000000 * TotalTests div TimeInMicro, + TestPerSecond = case TimeInMicro of + 0 -> + undefined; + _ -> + 1000000 * TotalTests div TimeInMicro + end, io:format("TC ~p ~p ~p ~p 1/s~n", [TC, Type, ParallellConnections, TestPerSecond]), unlink(SPid), SPid ! quit, {ok, TestPerSecond}. server_init(ssl, setup_connection, _, _, Server) -> - {ok, Socket} = ssl:listen(0, ssl_opts(listen)), - {ok, {_Host, Port}} = ssl:sockname(Socket), + {ok, LSocket} = ssl:listen(0, ssl_opts(listen)), + {ok, {_Host, Port}} = ssl:sockname(LSocket), {ok, Host} = inet:gethostname(), ?FPROF_SERVER andalso start_profile(fprof, [whereis(ssl_manager), new]), %%?EPROF_SERVER andalso start_profile(eprof, [ssl_connection_sup, ssl_manager]), ?EPROF_SERVER andalso start_profile(eprof, [ssl_manager]), Server ! {self(), {init, Host, Port}}, Test = fun(TSocket) -> - ok = ssl:ssl_accept(TSocket), - ssl:close(TSocket) + {ok, Socket} = ssl:handshake(TSocket), + ssl:close(Socket) end, - setup_server_connection(Socket, Test); + setup_server_connection(LSocket, Test); server_init(ssl, payload, Loop, _, Server) -> - {ok, Socket} = ssl:listen(0, ssl_opts(listen)), - {ok, {_Host, Port}} = ssl:sockname(Socket), + {ok, LSocket} = ssl:listen(0, ssl_opts(listen)), + {ok, {_Host, Port}} = ssl:sockname(LSocket), {ok, Host} = inet:gethostname(), Server ! {self(), {init, Host, Port}}, Test = fun(TSocket) -> - ok = ssl:ssl_accept(TSocket), + {ok, Socket} = ssl:handshake(TSocket), Size = byte_size(msg()), - server_echo(TSocket, Size, Loop), - ssl:close(TSocket) + server_echo(Socket, Size, Loop), + ssl:close(Socket) end, - setup_server_connection(Socket, Test); + setup_server_connection(LSocket, Test); server_init(ssl, pem_cache, Loop, _, Server) -> - {ok, Socket} = ssl:listen(0, ssl_opts(listen_der)), - {ok, {_Host, Port}} = ssl:sockname(Socket), + {ok, LSocket} = ssl:listen(0, ssl_opts(listen_der)), + {ok, {_Host, Port}} = ssl:sockname(LSocket), {ok, Host} = inet:gethostname(), Server ! {self(), {init, Host, Port}}, Test = fun(TSocket) -> - ok = ssl:ssl_accept(TSocket), + {ok, Socket} = ssl:handshake(TSocket), Size = byte_size(msg()), - server_echo(TSocket, Size, Loop), - ssl:close(TSocket) + server_echo(Socket, Size, Loop), + ssl:close(Socket) end, - setup_server_connection(Socket, Test); + setup_server_connection(LSocket, Test); server_init(Type, Tc, _, _, Server) -> io:format("No server init code for ~p ~p~n",[Type, Tc]), diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl new file mode 100644 index 0000000000..fb1695f38a --- /dev/null +++ b/lib/ssl/test/ssl_cert_SUITE.erl @@ -0,0 +1,563 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(ssl_cert_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [ + {'tlsv1.3', [], tls_1_3_protocol_groups()}, + {'tlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1.1', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1', [], pre_tls_1_3_protocol_groups()}, + {'sslv3', [], ssl_protocol_groups()}, + {'dtlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'dtlsv1', [], pre_tls_1_3_protocol_groups()}, + {rsa, [], all_version_tests()}, + {ecdsa, [], all_version_tests()}, + {dsa, [], all_version_tests()}, + {rsa_1_3, [], all_version_tests() ++ tls_1_3_tests() ++ [unsupported_sign_algo_client_auth, + unsupported_sign_algo_cert_client_auth]}, + {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} + ]. + +ssl_protocol_groups() -> + [{group, rsa}, + {group, dsa}]. + +pre_tls_1_3_protocol_groups() -> + [{group, rsa}, + {group, ecdsa}, + {group, dsa}]. + +tls_1_3_protocol_groups() -> + [{group, rsa_1_3}, + {group, ecdsa_1_3}]. + +tls_1_3_tests() -> + [ + hello_retry_request, + custom_groups, + hello_retry_client_auth, + hello_retry_client_auth_empty_cert_accepted, + hello_retry_client_auth_empty_cert_rejected + ]. + +all_version_tests() -> + [ + no_auth, + auth, + client_auth_empty_cert_accepted, + client_auth_empty_cert_rejected, + client_auth_partial_chain, + client_auth_allow_partial_chain, + client_auth_do_not_allow_partial_chain, + client_auth_partial_chain_fun_fail, + missing_root_cert_no_auth, + missing_root_cert_auth, + missing_root_cert_auth_user_verify_fun_accept, + missing_root_cert_auth_user_verify_fun_reject, + verify_fun_always_run_client, + verify_fun_always_run_server, + incomplete_chain_auth + %%invalid_signature_client + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(Group, Config0) when Group == rsa; + Group == rsa_1_3 -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; +init_per_group(Group, Config0) when Group == ecdsa; + Group == ecdsa_1_3 -> + + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + false -> + {skip, "Missing EC crypto support"} + end; + +init_per_group(Group, Config0) when Group == dsa -> + PKAlg = crypto:supports(public_keys), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + Config = ssl_test_lib:make_dsa_cert(Config0), + COpts = proplists:get_value(client_dsa_opts, Config), + SOpts = proplists:get_value(server_dsa_opts, Config), + [{cert_key_alg, dsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + false -> + {skip, "Missing DSS crypto support"} + end; +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + [{client_type, erlang}, + {server_type, erlang}, {version, GroupName} + | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +no_auth() -> + ssl_cert_tests:no_auth(). + +no_auth(Config) -> + ssl_cert_tests:no_auth(Config). +%%-------------------------------------------------------------------- +auth() -> + ssl_cert_tests:auth(). +auth(Config) -> + ssl_cert_tests:auth(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + ssl_cert_tests:client_auth_empty_cert_accepted(). +client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + ssl_cert_tests:client_auth_empty_cert_rejected(). +client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:client_auth_empty_cert_rejected(Config). +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + ssl_cert_tests:client_auth_partial_chain(). +client_auth_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + ssl_cert_tests:client_auth_allow_partial_chain(). +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_allow_partial_chain(Config). +%%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(). +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(). +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + ssl_cert_tests:missing_root_cert_no_auth(). +missing_root_cert_no_auth(Config) when is_list(Config) -> + ssl_cert_tests:missing_root_cert_no_auth(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_auth() -> + [{doc,"Must have ROOT certs to be able to verify verify peer"}]. +missing_root_cert_auth(Config) when is_list(Config) -> + ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_cert_opts, Config)), + {ClientNode, ServerNode, _} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [{verify, verify_peer} + | ServerOpts]}]), + + ssl_test_lib:check_result(Server, {error, {options, {cacertfile, ""}}}), + + ClientOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(client_cert_opts, Config)), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, + {from, self()}, + {options, [{verify, verify_peer} + | ClientOpts]}]), + + ssl_test_lib:check_result(Client, {error, {options, {cacertfile, ""}}}). + +%%-------------------------------------------------------------------- +missing_root_cert_auth_user_verify_fun_accept() -> + [{doc, "Test that the client succeds if the ROOT CA is unknown in verify_peer mode" + " with a verify_fun that accepts the unknown CA error"}]. + +missing_root_cert_auth_user_verify_fun_accept(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + FunAndState = {fun(_,{bad_cert, unknown_ca}, UserState) -> + {valid, UserState}; + (_,{bad_cert, _} = Reason, _) -> + {fail, Reason}; + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, []}, + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {verify_fun, FunAndState}], Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept() -> + [{doc, "Test old style verify fun"}]. + +missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc; + (Other, Acc) -> [Other | Acc] + end, + VerifyFun = + fun(ErrorList) -> + case lists:foldl(AcceptBadCa, [], ErrorList) of + [] -> true; + [_|_] -> false + end + end, + + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {verify_fun, VerifyFun}], Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +missing_root_cert_auth_user_verify_fun_reject() -> + [{doc, "Test that the client fails if the ROOT CA is unknown in verify_peer mode" + " with a verify_fun that rejects the unknown CA error"}]. + +missing_root_cert_auth_user_verify_fun_reject(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + FunAndState = {fun(_,{bad_cert, unknown_ca} = Reason, _UserState) -> + {fail, Reason}; + (_,{bad_cert, _} = Reason, _) -> + {fail, Reason}; + (_,{extension, UserState}, _) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, []}, + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {verify_fun, FunAndState}], Config), + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). +%%-------------------------------------------------------------------- +incomplete_chain_auth() -> + [{doc,"Test that we can verify an incompleat chain when we have the certs to rebuild it"}]. +incomplete_chain_auth(Config) when is_list(Config) -> + DefaultCertConf = ssl_test_lib:default_cert_chain_conf(), + #{client_config := ClientOpts0, + server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{server_chain, DefaultCertConf}, + {client_chain, DefaultCertConf}]), + [ServerRoot| _] = ServerCas = proplists:get_value(cacerts, ServerOpts0), + ClientCas = proplists:get_value(cacerts, ClientOpts0), + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {cacerts, ServerCas ++ ClientCas} | + proplists:delete(cacerts, ClientOpts0)], Config), + ServerOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {cacerts, [ServerRoot]} | + proplists:delete(cacerts, ServerOpts0)], Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +verify_fun_always_run_client() -> + [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. + +verify_fun_always_run_client(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + %% If user verify fun is called correctly we fail the connection. + %% otherwise we cannot tell this case apart form where we miss + %% to call users verify fun + FunAndState = {fun(_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, [ChainLen]) -> + {valid, [ChainLen + 1]}; + (_, valid_peer, [1]) -> + {fail, "verify_fun_was_always_run"}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, [0]}, + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, + [{verify, verify_peer}, + {verify_fun, FunAndState} + | ClientOpts]}]), + + ssl_test_lib:check_client_alert(Server, Client, handshake_failure). + +%%-------------------------------------------------------------------- +verify_fun_always_run_server() -> + [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. +verify_fun_always_run_server(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + %% If user verify fun is called correctly we fail the connection. + %% otherwise we cannot tell this case apart form where we miss + %% to call users verify fun + FunAndState = {fun(_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, [ChainLen]) -> + {valid, [ChainLen + 1]}; + (_, valid_peer, [1]) -> + {fail, "verify_fun_was_always_run"}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, [0]}, + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, + [{verify, verify_peer}, + {verify_fun, FunAndState} | + ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_client_alert(Server, Client, handshake_failure). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_client(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). +%%-------------------------------------------------------------------- +invalid_signature_server() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_server(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test cases ----------------------------------------------- +%%-------------------------------------------------------------------- +hello_retry_request() -> + [{doc,"Test that ssl server can request a new group when the client's first key share" + "is not supported"}]. + +hello_retry_request(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts0], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +custom_groups() -> + [{doc,"Test that ssl server can select a common group for key-exchange"}]. + +custom_groups(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, secp256r1, secp384r1]}|ServerOpts0], + ClientOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ClientOpts = [{supported_groups,[secp384r1, secp256r1, x25519]}|ClientOpts1], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +%% Triggers a Server Alert as ssl client does not have a certificate with a +%% signature algorithm supported by the server (signature_algorithms_cert extension +%% of CertificateRequest does not contain the algorithm of the client certificate). +%% ssl client sends an empty certificate. +unsupported_sign_algo_cert_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. + +unsupported_sign_algo_cert_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, + %% Skip rsa_pkcs1_sha256! + {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). + +%%-------------------------------------------------------------------- +unsupported_sign_algo_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. + +unsupported_sign_algo_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + %% Skip rsa_pkcs1_sha256! + {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + [{doc, "TLS 1.3 (HelloRetryRequest): Test client authentication."}]. + +hello_retry_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts0], + ServerOpts = [{verify, verify_peer}, + {fail_if_no_peer_cert, true} | ServerOpts1], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty " + "certificate and fail_if_no_peer_cert is set to true."}]. + +hello_retry_client_auth_empty_cert_accepted(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts2 = proplists:delete(keyfile, ClientOpts1), + + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts2], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client " + "sends an empty certificate and fail_if_no_peer_cert is set to true."}]. + +hello_retry_client_auth_empty_cert_rejected(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts2 = proplists:delete(keyfile, ClientOpts1), + + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts2], + + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl new file mode 100644 index 0000000000..c88daa2185 --- /dev/null +++ b/lib/ssl/test/ssl_cert_tests.erl @@ -0,0 +1,386 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(ssl_cert_tests). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +no_auth() -> + [{doc,"Test connection without authentication"}]. + +no_auth(Config) -> + ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +auth() -> + [{doc,"Test connection with mutual authentication"}]. + +auth(Config) -> + ClientOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + [{doc,"Test client authentication when client sends an empty certificate and " + "fail_if_no_peer_cert is set to false."}]. + +client_auth_empty_cert_accepted(Config) -> + ClientOpts = proplists:delete(keyfile, + proplists:delete(certfile, + ssl_test_lib:ssl_options(client_cert_opts, Config))), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{verify, verify_peer}, + {fail_if_no_peer_cert, false} | ServerOpts0], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + [{doc,"Test client authentication when client sends an empty certificate and " + "fail_if_no_peer_cert is set to true."}]. + +client_auth_empty_cert_rejected(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts0 = ssl_test_lib:ssl_options([], Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts = proplists:delete(keyfile, ClientOpts1), + + Version = proplists:get_value(version,Config), + case Version of + 'tlsv1.3' -> + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required); + _ -> + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, handshake_failure) + end. +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + [{doc, "Client sends an incompleate chain, by default not acceptable."}]. + +client_auth_partial_chain(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts0)), + [{_,RootCA,_} | _] = public_key:pem_decode(ClientCAs), + ClientOpts = [{cacerts, [RootCA]} | + proplists:delete(cacertfile, ClientOpts0)], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + [{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}]. + +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ClientCAs), + + PartialChain = fun(CertChain) -> + case lists:member(IntermidiateCA, CertChain) of + true -> + {trusted_ca, IntermidiateCA}; + false -> + unknown_ca + end + end, + ServerOpts = [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts0)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + + %%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + [{doc, "Server does not accept the chain sent by the client as ROOT CA is unkown, " + "and we do not choose to trust the intermediate CA. (partial_chain option)"}]. + +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(_CertChain) -> + unknown_ca + end, + ServerOpts = [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts0)], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + + %%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + [{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}]. + +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(_CertChain) -> + true = false %% crash on purpose + end, + ServerOpts = [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts0)], + + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + [{doc,"Test that the client succeds if the ROOT CA is unknown in verify_none mode"}]. + +missing_root_cert_no_auth(Config) -> + ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + [{doc,"Test server with invalid signature"}]. + +invalid_signature_client(Config) when is_list(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + + KeyFile = proplists:get_value(keyfile, ClientOpts0), + [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), + + ClientCertFile = proplists:get_value(certfile, ClientOpts0), + NewClientCertFile = filename:join(PrivDir, "client_invalid_cert.pem"), + [{'Certificate', ClientDerCert, _}] = ssl_test_lib:pem_to_der(ClientCertFile), + ClientOTPCert = public_key:pkix_decode_cert(ClientDerCert, otp), + ClientOTPTbsCert = ClientOTPCert#'OTPCertificate'.tbsCertificate, + NewClientDerCert = public_key:pkix_sign(ClientOTPTbsCert, Key), + ssl_test_lib:der_to_pem(NewClientCertFile, [{'Certificate', NewClientDerCert, not_encrypted}]), + ClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts0)], + ServerOpts = [{verify, verify_peer} | ServerOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +invalid_signature_server() -> + [{doc,"Test client with invalid signature"}]. + +invalid_signature_server(Config) when is_list(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + + KeyFile = proplists:get_value(keyfile, ServerOpts0), + [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), + + ServerCertFile = proplists:get_value(certfile, ServerOpts0), + NewServerCertFile = filename:join(PrivDir, "server_invalid_cert.pem"), + [{'Certificate', ServerDerCert, _}] = ssl_test_lib:pem_to_der(ServerCertFile), + ServerOTPCert = public_key:pkix_decode_cert(ServerDerCert, otp), + ServerOTPTbsCert = ServerOTPCert#'OTPCertificate'.tbsCertificate, + NewServerDerCert = public_key:pkix_sign(ServerOTPTbsCert, Key), + ssl_test_lib:der_to_pem(NewServerCertFile, [{'Certificate', NewServerDerCert, not_encrypted}]), + ServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts0)], + ClientOpts = [{verify, verify_peer} | ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +hello_retry_request() -> + [{doc,"Test that ssl server can request a new group when the client's first key share" + "is not supported"}]. + +hello_retry_request(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +custom_groups() -> + [{doc,"Test that ssl server can select a common group for key-exchange"}]. + +custom_groups(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config_custom(Config, + [{versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +%% Triggers a Server Alert as ssl client does not have a certificate with a +%% signature algorithm supported by the server (signature_algorithms_cert extension +%% of CertificateRequest does not contain the algorithm of the client certificate). +%% ssl client sends an empty certificate. +unsupported_sign_algo_cert_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. + +unsupported_sign_algo_cert_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, + %% Skip rsa_pkcs1_sha256! + {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). +%%-------------------------------------------------------------------- +unsupported_sign_algo_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. + +unsupported_sign_algo_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + %% Skip rsa_pkcs1_sha256! + {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + [{doc, "TLS 1.3 (HelloRetryRequest): Test client authentication."}]. + +hello_retry_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty " + "certificate and fail_if_no_peer_cert is set to false."}]. + +hello_retry_client_auth_empty_cert_accepted(Config) -> + ClientOpts0 = proplists:delete(keyfile, + proplists:delete(certfile, + ssl_test_lib:ssl_options(client_cert_opts, Config))), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client " + "sends an empty certificate and fail_if_no_peer_cert is set to true."}]. + +hello_retry_client_auth_empty_cert_rejected(Config) -> + ClientOpts0 = proplists:delete(keyfile, + proplists:delete(certfile, + ssl_test_lib:ssl_options(client_cert_opts, Config))), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]), + + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). + + +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------------------- +%%-------------------------------------------------------------------- + +group_config_custom(Config, ServerOpts, ClientOpts) -> + case proplists:get_value(client_type, Config) of + erlang -> + {[{groups,"X448:P-256:P-384"} | ServerOpts], + [{supported_groups, [secp384r1, secp256r1, x25519]} | ClientOpts]}; + openssl -> + {[{supported_groups, [x448, secp256r1, secp384r1]} | ServerOpts], + [{groups,"P-384:P-256:X25519"} | ClientOpts]} + end. + +group_config(Config, ServerOpts, ClientOpts) -> + case proplists:get_value(client_type, Config) of + erlang -> + {[{groups,"X448:X25519"} | ServerOpts], + [{supported_groups, [secp256r1, x25519]} | ClientOpts]}; + openssl -> + {[{supported_groups, [x448, x25519]} | ServerOpts], + [{groups,"P-256:X25519"} | ClientOpts]} + end. + +test_ciphers(_, 'tlsv1.3' = Version) -> + Ciphers = ssl:cipher_suites(default, Version), + ct:log("Version ~p Testing ~p~n", [Version, Ciphers]), + OpenSSLCiphers = openssl_ciphers(), + ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]), + lists:filter(fun(C) -> + ct:log("Cipher ~p~n", [C]), + lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers) + end, Ciphers); +test_ciphers(Kex, Version) -> + Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, Version), + [{key_exchange, Kex}]), + ct:log("Version ~p Testing ~p~n", [Version, Ciphers]), + OpenSSLCiphers = openssl_ciphers(), + ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]), + lists:filter(fun(C) -> + ct:log("Cipher ~p~n", [C]), + lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers) + end, Ciphers). + + + +openssl_ciphers() -> + Str = os:cmd("openssl ciphers"), + string:split(string:strip(Str, right, $\n), ":", all). diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index e89104a999..f38858e0bf 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -40,6 +40,7 @@ %%-------------------------------------------------------------------- all() -> [ + {group, 'tlsv1.3'}, {group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, @@ -50,6 +51,7 @@ all() -> groups() -> [ + {'tlsv1.3', [], all_protocol_groups()}, {'tlsv1.2', [], all_protocol_groups()}, {'tlsv1.1', [], all_protocol_groups()}, {'tlsv1', [], all_protocol_groups()}, @@ -69,36 +71,20 @@ all_protocol_groups() -> {group, error_handling}]. tests() -> - [verify_peer, - verify_none, - server_require_peer_cert_ok, - server_require_peer_cert_fail, - server_require_peer_cert_empty_ok, - server_require_peer_cert_partial_chain, - server_require_peer_cert_allow_partial_chain, - server_require_peer_cert_do_not_allow_partial_chain, - server_require_peer_cert_partial_chain_fun_fail, - verify_fun_always_run_client, - verify_fun_always_run_server, - cert_expired, - invalid_signature_client, - invalid_signature_server, + [cert_expired, + %invalid_signature_client, + %%invalid_signature_server, extended_key_usage_verify_both, extended_key_usage_verify_server, critical_extension_verify_client, critical_extension_verify_server, critical_extension_verify_none, - customize_hostname_check, - incomplete_chain + long_chain ]. error_handling_tests()-> [client_with_cert_cipher_suites_handshake, - server_verify_no_cacerts, - unknown_server_ca_fail, - unknown_server_ca_accept_verify_none, - unknown_server_ca_accept_verify_peer, - unknown_server_ca_accept_backwardscompatibility, + %%unknown_server_ca_accept_backwardscompatibility, no_authority_key_identifier, no_authority_key_identifier_keyEncipherment]. @@ -147,6 +133,7 @@ init_per_testcase(_TestCase, Config) -> ssl:stop(), ssl:start(), ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:pal(" ~p", [ dtls_record:supported_protocol_versions()]), ct:timetrap({seconds, 10}), Config. @@ -156,61 +143,6 @@ end_per_testcase(_TestCase, Config) -> %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- - -verify_peer() -> - [{doc,"Test option verify_peer"}]. -verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_peer} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_peer} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -verify_none() -> - [{doc,"Test option verify_none"}]. - -verify_none(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_none} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, - {verify, verify_none} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - server_verify_client_once() -> [{doc,"Test server option verify_client_once"}]. @@ -249,303 +181,6 @@ server_verify_client_once(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -server_require_peer_cert_ok() -> - [{doc,"Test server option fail_if_no_peer_cert when peer sends cert"}]. - -server_require_peer_cert_ok(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -server_require_peer_cert_fail() -> - [{doc,"Test server option fail_if_no_peer_cert when peer doesn't send cert"}]. - -server_require_peer_cert_fail(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - BadClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - Active = proplists:get_value(active, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [{active, Active} | ServerOpts]}]), - - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{active, Active} | BadClientOpts]}]), - - ssl_test_lib:check_server_alert(Server, Client, handshake_failure). - -%%-------------------------------------------------------------------- -server_require_peer_cert_empty_ok() -> - [{doc,"Test server option fail_if_no_peer_cert when peer sends cert"}]. - -server_require_peer_cert_empty_ok(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, false} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - ClientOpts = proplists:delete(keyfile, proplists:delete(certfile, ClientOpts0)), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -server_require_peer_cert_partial_chain() -> - [{doc, "Client sends an incompleate chain, by default not acceptable."}]. - -server_require_peer_cert_partial_chain(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - Active = proplists:get_value(active, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), - [{_,RootCA,_} | _] = public_key:pem_decode(ClientCAs), - - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, Active} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, Active}, - {cacerts, [RootCA]} | - proplists:delete(cacertfile, ClientOpts)]}]), - ssl_test_lib:check_server_alert(Server, Client, unknown_ca). - -%%-------------------------------------------------------------------- -server_require_peer_cert_allow_partial_chain() -> - [{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}]. - -server_require_peer_cert_allow_partial_chain(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - - {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), - [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ClientCAs), - - PartialChain = fun(CertChain) -> - case lists:member(IntermidiateCA, CertChain) of - true -> - {trusted_ca, IntermidiateCA}; - false -> - unknown_ca - end - end, - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, - {cacerts, [IntermidiateCA]}, - {partial_chain, PartialChain} | - proplists:delete(cacertfile, ServerOpts)]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - %%-------------------------------------------------------------------- -server_require_peer_cert_do_not_allow_partial_chain() -> - [{doc, "Server does not accept the chain sent by the client as ROOT CA is unkown, " - "and we do not choose to trust the intermediate CA. (partial_chain option)"}]. - -server_require_peer_cert_do_not_allow_partial_chain(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), - [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), - - PartialChain = fun(_CertChain) -> - unknown_ca - end, - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{cacerts, [IntermidiateCA]}, - {partial_chain, PartialChain} | - proplists:delete(cacertfile, ServerOpts)]}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_server_alert(Server, Client, unknown_ca). - %%-------------------------------------------------------------------- -server_require_peer_cert_partial_chain_fun_fail() -> - [{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}]. - -server_require_peer_cert_partial_chain_fun_fail(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), - [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), - - PartialChain = fun(_CertChain) -> - true = false %% crash on purpose - end, - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{cacerts, [IntermidiateCA]}, - {partial_chain, PartialChain} | - proplists:delete(cacertfile, ServerOpts)]}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_server_alert(Server, Client, unknown_ca). - -%%-------------------------------------------------------------------- -verify_fun_always_run_client() -> - [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. - -verify_fun_always_run_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - %% If user verify fun is called correctly we fail the connection. - %% otherwise we can not tell this case apart form where we miss - %% to call users verify fun - FunAndState = {fun(_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, [ChainLen]) -> - {valid, [ChainLen + 1]}; - (_, valid_peer, [1]) -> - {fail, "verify_fun_was_always_run"}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, [0]}, - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState} - | ClientOpts]}]), - - ssl_test_lib:check_client_alert(Server, Client, handshake_failure). - -%%-------------------------------------------------------------------- -verify_fun_always_run_server() -> - [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. -verify_fun_always_run_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% If user verify fun is called correctly we fail the connection. - %% otherwise we can not tell this case apart form where we miss - %% to call users verify fun - FunAndState = {fun(_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, [ChainLen]) -> - {valid, [ChainLen + 1]}; - (_, valid_peer, [1]) -> - {fail, "verify_fun_was_always_run"}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, [0]}, - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState} | - ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_client_alert(Server, Client, handshake_failure). -%%-------------------------------------------------------------------- - cert_expired() -> [{doc,"Test server with expired certificate"}]. @@ -852,6 +487,7 @@ invalid_signature_server(Config) when is_list(Config) -> {from, self()}, {options, [{verify, verify_peer} | ClientOpts]}]), ssl_test_lib:check_server_alert(Server, Client, unknown_ca). + %%-------------------------------------------------------------------- invalid_signature_client() -> @@ -926,214 +562,22 @@ client_with_cert_cipher_suites_handshake(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -server_verify_no_cacerts() -> - [{doc,"Test server must have cacerts if it wants to verify client"}]. -server_verify_no_cacerts(Config) when is_list(Config) -> - ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_rsa_opts, Config)), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [{verify, verify_peer} - | ServerOpts]}]), - - ssl_test_lib:check_result(Server, {error, {options, {cacertfile, ""}}}). - - -%%-------------------------------------------------------------------- -unknown_server_ca_fail() -> - [{doc,"Test that the client fails if the ca is unknown in verify_peer mode"}]. -unknown_server_ca_fail(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - FunAndState = {fun(_,{bad_cert, unknown_ca} = Reason, _) -> - {fail, Reason}; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, [test_to_update_user_state | UserState]}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, []}, - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState} - | ClientOpts]}]), - ssl_test_lib:check_client_alert(Server, Client, unknown_ca). -%%-------------------------------------------------------------------- -unknown_server_ca_accept_verify_none() -> - [{doc,"Test that the client succeds if the ca is unknown in verify_none mode"}]. -unknown_server_ca_accept_verify_none(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, - [{verify, verify_none}| ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -unknown_server_ca_accept_verify_peer() -> - [{doc, "Test that the client succeds if the ca is unknown in verify_peer mode" - " with a verify_fun that accepts the unknown ca error"}]. -unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - FunAndState = {fun(_,{bad_cert, unknown_ca}, UserState) -> - {valid, UserState}; - (_,{bad_cert, _} = Reason, _) -> - {fail, Reason}; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, UserState}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, []}, - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState}| ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -unknown_server_ca_accept_backwardscompatibility() -> - [{doc,"Test that old style verify_funs will work"}]. -unknown_server_ca_accept_backwardscompatibility(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc; - (Other, Acc) -> [Other | Acc] - end, - VerifyFun = - fun(ErrorList) -> - case lists:foldl(AcceptBadCa, [], ErrorList) of - [] -> true; - [_|_] -> false - end - end, - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, VerifyFun}| ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -customize_hostname_check() -> - [{doc,"Test option customize_hostname_check."}]. -customize_hostname_check(Config) when is_list(Config) -> - Ext = [#'Extension'{extnID = ?'id-ce-subjectAltName', - extnValue = [{dNSName, "*.example.org"}], - critical = false} - ], - {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_chain, - [[], - [], - [{extensions, Ext}] - ]}], - Config, "https_hostname_convention"), - ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), - ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - CustomFun = public_key:pkix_verify_hostname_match_fun(https), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{server_name_indication, "other.example.org"}, - {customize_hostname_check, - [{match_fun, CustomFun}]} | ClientOpts] - }]), - ssl_test_lib:check_result(Server, ok, Client, ok), - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts} - ]), - ssl_test_lib:check_client_alert(Server, Client1, handshake_failure). - -incomplete_chain() -> +long_chain() -> [{doc,"Test option verify_peer"}]. -incomplete_chain(Config) when is_list(Config) -> - DefConf = ssl_test_lib:default_cert_chain_conf(), - CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf), +long_chain(Config) when is_list(Config) -> #{server_config := ServerConf, - client_config := ClientConf} = public_key:pkix_test_data(CertChainConf), + client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}], + [{key, ssl_test_lib:hardcode_rsa_key(3)}], + [{key, ssl_test_lib:hardcode_rsa_key(4)}]], + peer => [{key, ssl_test_lib:hardcode_rsa_key(5)}]}, + client_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(3)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]], + peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]}}), [ServerRoot| _] = ServerCas = proplists:get_value(cacerts, ServerConf), ClientCas = proplists:get_value(cacerts, ClientConf), - + Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -1150,6 +594,7 @@ incomplete_chain(Config) when is_list(Config) -> {mfa, {ssl_test_lib, ReceiveFunction, []}}, {options, [{active, Active}, {verify, verify_peer}, + {depth, 5}, {cacerts, ServerCas ++ ClientCas} | proplists:delete(cacerts, ClientConf)]}]), ssl_test_lib:check_result(Server, ok, Client, ok), diff --git a/lib/ssl/test/ssl_cipher_suite_SUITE.erl b/lib/ssl/test/ssl_cipher_suite_SUITE.erl new file mode 100644 index 0000000000..e598d662e9 --- /dev/null +++ b/lib/ssl/test/ssl_cipher_suite_SUITE.erl @@ -0,0 +1,777 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_cipher_suite_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [ + {'tlsv1.2', [], kex()}, + {'tlsv1.1', [], kex()}, + {'tlsv1', [], kex()}, + {'sslv3', [], ssl3_kex()}, + {'dtlsv1.2', [], kex()}, + {'dtlsv1', [], kex()}, + {dhe_rsa, [],[dhe_rsa_3des_ede_cbc, + dhe_rsa_aes_128_cbc, + dhe_rsa_aes_256_cbc, + dhe_rsa_chacha20_poly1305 + ]}, + {ecdhe_rsa, [], [ecdhe_rsa_3des_ede_cbc, + ecdhe_rsa_aes_128_cbc, + ecdhe_rsa_aes_128_gcm, + ecdhe_rsa_aes_256_cbc, + ecdhe_rsa_aes_256_gcm, + ecdhe_rsa_chacha20_poly1305 + ]}, + {ecdhe_ecdsa, [],[ecdhe_ecdsa_rc4_128, + ecdhe_ecdsa_3des_ede_cbc, + ecdhe_ecdsa_aes_128_cbc, + ecdhe_ecdsa_aes_128_gcm, + ecdhe_ecdsa_aes_256_cbc, + ecdhe_ecdsa_aes_256_gcm, + ecdhe_ecdsa_chacha20_poly1305 + ]}, + {rsa, [], [rsa_3des_ede_cbc, + rsa_aes_128_cbc, + rsa_aes_256_cbc, + rsa_rc4_128 + ]}, + {dhe_dss, [], [dhe_dss_3des_ede_cbc, + dhe_dss_aes_128_cbc, + dhe_dss_aes_256_cbc]}, + {srp_rsa, [], [srp_rsa_3des_ede_cbc, + srp_rsa_aes_128_cbc, + srp_rsa_aes_256_cbc]}, + {srp_dss, [], [srp_dss_3des_ede_cbc, + srp_dss_aes_128_cbc, + srp_dss_aes_256_cbc]}, + {rsa_psk, [], [rsa_psk_3des_ede_cbc, + rsa_psk_rc4_128, + rsa_psk_aes_128_cbc, + rsa_psk_aes_256_cbc + ]}, + {dh_anon, [], [dh_anon_rc4_128, + dh_anon_3des_ede_cbc, + dh_anon_aes_128_cbc, + dh_anon_aes_128_gcm, + dh_anon_aes_256_cbc, + dh_anon_aes_256_gcm]}, + {ecdh_anon, [], [ecdh_anon_3des_ede_cbc, + ecdh_anon_aes_128_cbc, + ecdh_anon_aes_256_cbc + ]}, + {srp_anon, [], [srp_anon_3des_ede_cbc, + srp_anon_aes_128_cbc, + srp_anon_aes_256_cbc]}, + {psk, [], [psk_3des_ede_cbc, + psk_rc4_128, + psk_aes_128_cbc, + psk_aes_128_ccm, + psk_aes_128_ccm_8, + psk_aes_256_cbc, + psk_aes_256_ccm, + psk_aes_256_ccm_8 + ]}, + {dhe_psk, [], [dhe_psk_3des_ede_cbc, + dhe_psk_rc4_128, + dhe_psk_aes_128_cbc, + dhe_psk_aes_128_ccm, + dhe_psk_aes_128_ccm_8, + dhe_psk_aes_256_cbc, + dhe_psk_aes_256_ccm, + dhe_psk_aes_256_ccm_8 + ]}, + {ecdhe_psk, [], [ecdhe_psk_3des_ede_cbc, + ecdhe_psk_rc4_128, + ecdhe_psk_aes_128_cbc, + ecdhe_psk_aes_128_ccm, + ecdhe_psk_aes_128_ccm_8, + ecdhe_psk_aes_256_cbc + ]} + ]. + +kex() -> + rsa() ++ ecdsa() ++ dss() ++ anonymous(). + + +ssl3_kex() -> + ssl3_rsa() ++ ssl3_dss() ++ ssl3_anonymous(). + + +rsa() -> + [{group, dhe_rsa}, + {group, ecdhe_rsa}, + {group, rsa}, + {group, srp_rsa}, + {group, rsa_psk} + ]. + +ssl3_rsa() -> + [{group, dhe_rsa}, + {group, rsa} + ]. + +ecdsa() -> + [{group, ecdhe_ecdsa}]. + +dss() -> + [{group, dhe_dss}, + {group, srp_dss}]. + +ssl3_dss() -> + [{group, dhe_dss} + ]. + +anonymous() -> + [{group, dh_anon}, + {group, ecdh_anon}, + {group, psk}, + {group, dhe_psk}, + {group, ecdhe_psk}, + {group, srp_anon} + ]. + +ssl3_anonymous() -> + [{group, dh_anon}]. + + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + + +init_per_group(GroupName, Config) when GroupName == ecdh_anon; + GroupName == ecdhe_rsa; + GroupName == ecdhe_psk -> + case proplists:get_bool(ecdh, proplists:get_value(public_keys, crypto:supports())) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(ecdhe_ecdsa = GroupName, Config) -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(ecdh, PKAlg) andalso lists:member(ecdsa, PKAlg) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(dhe_dss = GroupName, Config) -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing DSS crypto support"} + end; +init_per_group(srp_dss = GroupName, Config) -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(dss, PKAlg) andalso lists:member(srp, PKAlg) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing DSS_SRP crypto support"} + end; +init_per_group(GroupName, Config) when GroupName == srp_anon; + GroupName == srp_rsa -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(srp, PKAlg) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing SRP crypto support"} + end; +init_per_group(dhe_psk = GroupName, Config) -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(dh, PKAlg) of + true -> + init_certs(GroupName, Config); + false -> + {skip, "Missing SRP crypto support"} + end; +init_per_group(GroupName, Config0) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, end_per_group(GroupName, Config0)); + false -> + init_certs(GroupName, Config0) + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) when TestCase == psk_3des_ede_cbc; + TestCase == srp_anon_3des_ede_cbc; + TestCase == dhe_psk_3des_ede_cbc; + TestCase == ecdhe_psk_3des_ede_cbc; + TestCase == srp_rsa_3des_ede_cbc; + TestCase == srp_dss_3des_ede_cbc; + TestCase == rsa_psk_3des_ede_cbc; + TestCase == rsa_3des_ede_cbc; + TestCase == dhe_rsa_3des_ede_cbc; + TestCase == dhe_dss_3des_ede_cbc; + TestCase == ecdhe_rsa_3des_ede_cbc; + TestCase == srp_anon_dss_3des_ede_cbc; + TestCase == dh_anon_3des_ede_cbc; + TestCase == ecdh_anon_3des_ede_cbc; + TestCase == ecdhe_ecdsa_3des_ede_cbc -> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(des_ede3, SupCiphers) of + true -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, "Missing 3DES crypto support"} + end; +init_per_testcase(TestCase, Config) when TestCase == psk_rc4_128; + TestCase == ecdhe_psk_rc4_128; + TestCase == dhe_psk_rc4_128; + TestCase == rsa_psk_rc4_128; + TestCase == rsa_rc4_128; + TestCase == ecdhe_rsa_rc4_128; + TestCase == ecdhe_ecdsa_rc4_128; + TestCase == dh_anon_rc4_128 -> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(rc4, SupCiphers) of + true -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, "Missing RC4 crypto support"} + end; +init_per_testcase(TestCase, Config) when TestCase == psk_aes_128_ccm_8; + TestCase == rsa_psk_aes_128_ccm_8; + TestCase == psk_aes_128_ccm_8; + TestCase == dhe_psk_aes_128_ccm_8; + TestCase == ecdhe_psk_aes_128_ccm_8 -> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(aes_128_ccm, SupCiphers) of + true -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, "Missing AES_128_CCM crypto support"} + end; +init_per_testcase(TestCase, Config) when TestCase == psk_aes_256_ccm_8; + TestCase == rsa_psk_aes_256_ccm_8; + TestCase == psk_aes_256_ccm_8; + TestCase == dhe_psk_aes_256_ccm_8; + TestCase == ecdhe_psk_aes_256_ccm_8 -> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(aes_256_ccm, SupCiphers) of + true -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, "Missing AES_256_CCM crypto support"} + end; +init_per_testcase(TestCase, Config) -> + Cipher = ssl_test_lib:test_cipher(TestCase, Config), + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(Cipher, SupCiphers) of + true -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, {Cipher, SupCiphers}} + end. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Initializtion ------------------------------------------ +%%-------------------------------------------------------------------- + +init_certs(srp_rsa, Config) -> + DefConf = ssl_test_lib:default_cert_chain_conf(), + CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf), + #{server_config := ServerOpts, + client_config := ClientOpts} + = public_key:pkix_test_data(CertChainConf), + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}} | ServerOpts], + client_config => [{srp_identity, {"Test-User", "secret"}} | ClientOpts]}} | + proplists:delete(tls_config, Config)]; +init_certs(srp_anon, Config) -> + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}], + client_config => [{srp_identity, {"Test-User", "secret"}}]}} | + proplists:delete(tls_config, Config)]; +init_certs(rsa_psk, Config) -> + ClientExt = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]), + {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain, + [[],[],[{extensions, ClientExt}]]}], + Config, "_peer_keyEncipherment"), + PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}} | ServerOpts], + client_config => [{psk_identity, "Test-User"}, + {user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}} | ClientOpts]}} | + proplists:delete(tls_config, Config)]; +init_certs(rsa, Config) -> + ClientExt = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]), + {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain, + [[],[],[{extensions, ClientExt}]]}], + Config, "_peer_keyEncipherment"), + [{tls_config, #{server_config => ServerOpts, + client_config => ClientOpts}} | + proplists:delete(tls_config, Config)]; +init_certs(dhe_dss, Config) -> + DefConf = ssl_test_lib:default_cert_chain_conf(), + CertChainConf = ssl_test_lib:gen_conf(dsa, dsa, DefConf, DefConf), + #{server_config := ServerOpts, + client_config := ClientOpts} + = public_key:pkix_test_data(CertChainConf), + [{tls_config, #{server_config => ServerOpts, + client_config => ClientOpts}} | + proplists:delete(tls_config, Config)]; +init_certs(srp_dss, Config) -> + DefConf = ssl_test_lib:default_cert_chain_conf(), + CertChainConf = ssl_test_lib:gen_conf(dsa, dsa, DefConf, DefConf), + #{server_config := ServerOpts, + client_config := ClientOpts} + = public_key:pkix_test_data(CertChainConf), + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}} | ServerOpts], + client_config => [{srp_identity, {"Test-User", "secret"}} | ClientOpts]}} | + proplists:delete(tls_config, Config)]; +init_certs(GroupName, Config) when GroupName == dhe_rsa; + GroupName == ecdhe_rsa -> + DefConf = ssl_test_lib:default_cert_chain_conf(), + CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf), + #{server_config := ServerOpts, + client_config := ClientOpts} + = public_key:pkix_test_data(CertChainConf), + [{tls_config, #{server_config => ServerOpts, + client_config => ClientOpts}} | + proplists:delete(tls_config, Config)]; +init_certs(GroupName, Config) when GroupName == dhe_ecdsa; + GroupName == ecdhe_ecdsa -> + DefConf = ssl_test_lib:default_cert_chain_conf(), + CertChainConf = ssl_test_lib:gen_conf(ecdsa, ecdsa, DefConf, DefConf), + #{server_config := ServerOpts, + client_config := ClientOpts} + = public_key:pkix_test_data(CertChainConf), + [{tls_config, #{server_config => ServerOpts, + client_config => ClientOpts}} | + proplists:delete(tls_config, Config)]; +init_certs(GroupName, Config) when GroupName == psk; + GroupName == dhe_psk; + GroupName == ecdhe_psk -> + PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}}], + client_config => [{psk_identity, "Test-User"}, + {user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}}]}} | + proplists:delete(tls_config, Config)]; +init_certs(srp, Config) -> + [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}], + client_config => [{srp_identity, {"Test-User", "secret"}}]}} | + proplists:delete(tls_config, Config)]; +init_certs(_GroupName, Config) -> + %% Anonymous does not need certs + [{tls_config, #{server_config => [], + client_config => []}} | + proplists:delete(tls_config, Config)]. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% SRP -------------------------------------------------------- +%%-------------------------------------------------------------------- +srp_rsa_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_rsa, '3des_ede_cbc', Config). + +srp_rsa_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_rsa, 'aes_128_cbc', Config). + +srp_rsa_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_rsa, 'aes_256_cbc', Config). + +srp_dss_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_dss, '3des_ede_cbc', Config). + +srp_dss_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_dss, 'aes_128_cbc', Config). + +srp_dss_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_dss, 'aes_256_cbc', Config). + +%%-------------------------------------------------------------------- +%% PSK -------------------------------------------------------- +%%-------------------------------------------------------------------- +rsa_psk_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, '3des_ede_cbc', Config). + +rsa_psk_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_128_cbc', Config). + +rsa_psk_aes_128_ccm(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_128_ccm', Config). + +rsa_psk_aes_128_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_128_ccm_8', Config). + +rsa_psk_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_256_cbc', Config). + +rsa_psk_aes_256_ccm(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_256_ccm', Config). + +rsa_psk_aes_256_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'aes_256_ccm_8', Config). + +rsa_psk_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(rsa_psk, 'rc4_128', Config). + +%%-------------------------------------------------------------------- +%% RSA -------------------------------------------------------- +%%-------------------------------------------------------------------- +rsa_des_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'des_cbc', Config). + +rsa_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa, '3des_ede_cbc', Config). + +rsa_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'aes_128_cbc', Config). + +rsa_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'aes_256_cbc', Config). + +rsa_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'aes_128_gcm', Config). + +rsa_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'aes_256_gcm', Config). + +rsa_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(rsa, 'rc4_128', Config). +%%-------------------------------------------------------------------- +%% DHE_RSA -------------------------------------------------------- +%%-------------------------------------------------------------------- +dhe_rsa_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, '3des_ede_cbc', Config). + +dhe_rsa_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, 'aes_128_cbc', Config). + +dhe_rsa_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, 'aes_128_gcm', Config). + +dhe_rsa_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, 'aes_256_cbc', Config). + +dhe_rsa_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, 'aes_256_gcm', Config). + +dhe_rsa_chacha20_poly1305(Config) when is_list(Config) -> + run_ciphers_test(dhe_rsa, 'chacha20_poly1305', Config). +%%-------------------------------------------------------------------- +%% ECDHE_RSA -------------------------------------------------------- +%%-------------------------------------------------------------------- +ecdhe_rsa_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, '3des_ede_cbc', Config). + +ecdhe_rsa_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'aes_128_cbc', Config). + +ecdhe_rsa_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'aes_128_gcm', Config). + +ecdhe_rsa_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'aes_256_cbc', Config). + +ecdhe_rsa_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'aes_256_gcm', Config). + +ecdhe_rsa_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'rc4_128', Config). + +ecdhe_rsa_chacha20_poly1305(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_rsa, 'chacha20_poly1305', Config). + +%%-------------------------------------------------------------------- +%% ECDHE_ECDSA -------------------------------------------------------- +%%-------------------------------------------------------------------- +ecdhe_ecdsa_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'rc4_128', Config). + +ecdhe_ecdsa_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, '3des_ede_cbc', Config). + +ecdhe_ecdsa_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_128_cbc', Config). + +ecdhe_ecdsa_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_128_gcm', Config). + +ecdhe_ecdsa_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_256_cbc', Config). + +ecdhe_ecdsa_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_256_gcm', Config). + +ecdhe_ecdsa_chacha20_poly1305(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'chacha20_poly1305', Config). +%%-------------------------------------------------------------------- +%% DHE_DSS -------------------------------------------------------- +%%-------------------------------------------------------------------- +dhe_dss_des_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, 'des_cbc', Config). + +dhe_dss_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, '3des_ede_cbc', Config). + +dhe_dss_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, 'aes_128_cbc', Config). + +dhe_dss_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, 'aes_256_cbc', Config). + +dhe_dss_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, 'aes_128_gcm', Config). + +dhe_dss_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_dss, 'aes_256_gcm', Config). + +%%-------------------------------------------------------------------- +%% Anonymous -------------------------------------------------------- +%%-------------------------------------------------------------------- +dh_anon_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, '3des_ede_cbc', Config). + +dh_anon_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, 'aes_128_cbc', Config). + +dh_anon_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, 'aes_128_gcm', Config). + +dh_anon_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, 'aes_256_cbc', Config). + +dh_anon_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, 'aes_256_gcm', Config). + +dh_anon_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(dh_anon, 'rc4_128', Config). + +ecdh_anon_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdh_anon, '3des_ede_cbc', Config). + +ecdh_anon_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdh_anon, 'aes_128_cbc', Config). + +ecdh_anon_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdh_anon, 'aes_256_cbc', Config). + +srp_anon_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_anon, '3des_ede_cbc', Config). + +srp_anon_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_anon, 'aes_128_cbc', Config). + +srp_anon_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(srp_anon, 'aes_256_cbc', Config). + +dhe_psk_des_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'des_cbc', Config). + +dhe_psk_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'rc4_128', Config). + +dhe_psk_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, '3des_ede_cbc', Config). + +dhe_psk_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_128_cbc', Config). + +dhe_psk_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_256_cbc', Config). + +dhe_psk_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_128_gcm', Config). + +dhe_psk_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_256_gcm', Config). + +dhe_psk_aes_128_ccm(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_128_ccm', Config). + +dhe_psk_aes_256_ccm(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_256_ccm', Config). + +dhe_psk_aes_128_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_128_ccm_8', Config). + +dhe_psk_aes_256_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(dhe_psk, 'aes_256_ccm_8', Config). + +ecdhe_psk_des_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'des_cbc', Config). + +ecdhe_psk_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'rc4_128', Config). + +ecdhe_psk_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, '3des_ede_cbc', Config). + +ecdhe_psk_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_128_cbc', Config). + +ecdhe_psk_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_256_cbc', Config). + +ecdhe_psk_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_128_gcm', Config). + +ecdhe_psk_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_256_gcm', Config). + +ecdhe_psk_aes_128_ccm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_128_ccm', Config). + +ecdhe_psk_aes_128_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_psk, 'aes_128_ccm_8', Config). + +psk_des_cbc(Config) when is_list(Config) -> + run_ciphers_test(psk, 'des_cbc', Config). + +psk_rc4_128(Config) when is_list(Config) -> + run_ciphers_test(psk, 'rc4_128', Config). + +psk_3des_ede_cbc(Config) when is_list(Config) -> + run_ciphers_test(psk, '3des_ede_cbc', Config). + +psk_aes_128_cbc(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_128_cbc', Config). + +psk_aes_256_cbc(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_256_cbc', Config). + +psk_aes_128_gcm(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_128_gcm', Config). + +psk_aes_256_gcm(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_256_gcm', Config). + +psk_aes_128_ccm(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_128_ccm', Config). + +psk_aes_256_ccm(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_256_ccm', Config). + +psk_aes_128_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_128_ccm_8', Config). + +psk_aes_256_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(psk, 'aes_256_ccm_8', Config). + +%%-------------------------------------------------------------------- +%% Internal functions ---------------------------------------------- +%%-------------------------------------------------------------------- +run_ciphers_test(Kex, Cipher, Config) -> + Version = ssl_test_lib:protocol_version(Config), + TestCiphers = test_ciphers(Kex, Cipher, Version), + + case TestCiphers of + [_|_] -> + lists:foreach(fun(TestCipher) -> + cipher_suite_test(TestCipher, Version, Config) + end, TestCiphers); + [] -> + {skip, {not_sup, Kex, Cipher, Version}} + end. + +cipher_suite_test(ErlangCipherSuite, Version, Config) -> + #{server_config := SOpts, + client_config := COpts} = proplists:get_value(tls_config, Config), + ServerOpts = ssl_test_lib:ssl_options(SOpts, Config), + ClientOpts = ssl_test_lib:ssl_options(COpts, Config), + ct:log("Testing CipherSuite ~p~n", [ErlangCipherSuite]), + ct:log("Server Opts ~p~n", [ServerOpts]), + ct:log("Client Opts ~p~n", [ClientOpts]), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + ConnectionInfo = {ok, {Version, ErlangCipherSuite}}, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, cipher_result, [ConnectionInfo]}}, + {options, [{versions, [Version]}, {ciphers, [ErlangCipherSuite]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, cipher_result, [ConnectionInfo]}}, + {options, [{versions, [Version]}, {ciphers, [ErlangCipherSuite]} | + ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +test_ciphers(Kex, Cipher, Version) -> + ssl:filter_cipher_suites(ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version), + [{key_exchange, + fun(Kex0) when Kex0 == Kex -> true; + (_) -> false + end}, + {cipher, + fun(Cipher0) when Cipher0 == Cipher -> true; + (_) -> false + end}]). + diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 003e1fc448..7cfb2ac0c5 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2018. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -311,9 +311,11 @@ listen_port_options(Config) when is_list(Config) -> catch _:Reason -> stop_ssl_node(NH2), + stop_ssl_node(NH1), ct:fail(Reason) end, stop_ssl_node(NH2), + stop_ssl_node(NH1), success(Config). %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl index 618ad0789e..67944c74d2 100644 --- a/lib/ssl/test/ssl_dist_bench_SUITE.erl +++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl @@ -32,6 +32,7 @@ -export( [setup/1, roundtrip/1, + sched_utilization/1, throughput_0/1, throughput_64/1, throughput_1024/1, @@ -48,14 +49,19 @@ suite() -> [{ct_hooks, [{ts_install_cth, [{nodenames, 2}]}]}]. -all() -> [{group, ssl}, {group, plain}]. +all() -> + [{group, ssl}, + {group, crypto}, + {group, plain}]. groups() -> [{ssl, all_groups()}, + {crypto, all_groups()}, {plain, all_groups()}, %% {setup, [{repeat, 1}], [setup]}, {roundtrip, [{repeat, 1}], [roundtrip]}, + {sched_utilization,[{repeat, 1}], [sched_utilization]}, {throughput, [{repeat, 1}], [throughput_0, throughput_64, @@ -69,7 +75,9 @@ groups() -> all_groups() -> [{group, setup}, {group, roundtrip}, - {group, throughput}]. + {group, throughput}, + {group, sched_utilization} + ]. init_per_suite(Config) -> Digest = sha1, @@ -160,6 +168,11 @@ end_per_suite(Config) -> init_per_group(ssl, Config) -> [{ssl_dist, true}, {ssl_dist_prefix, "SSL"}|Config]; +init_per_group(crypto, Config) -> + [{ssl_dist, false}, {ssl_dist_prefix, "Crypto"}, + {ssl_dist_args, + "-proto_dist inet_crypto"} + |Config]; init_per_group(plain, Config) -> [{ssl_dist, false}, {ssl_dist_prefix, "Plain"}|Config]; init_per_group(_GroupName, Config) -> @@ -351,6 +364,120 @@ roundtrip_client(Pid, Mon, StartTime, N) -> roundtrip_client(Pid, Mon, StartTime, N - 1) end. +%%--------------------------------------- +%% Scheduler utilization at constant load + + +sched_utilization(Config) -> + run_nodepair_test( + fun(A, B, Prefix, HA, HB) -> + sched_utilization(A, B, Prefix, HA, HB, proplists:get_value(ssl_dist, Config)) + end, Config). + +sched_utilization(A, B, Prefix, HA, HB, SSL) -> + [] = ssl_apply(HA, erlang, nodes, []), + [] = ssl_apply(HB, erlang, nodes, []), + {ClientMsacc, ServerMsacc, Msgs} = + ssl_apply(HA, fun () -> sched_util_runner(A, B, SSL) end), + [B] = ssl_apply(HA, erlang, nodes, []), + [A] = ssl_apply(HB, erlang, nodes, []), + msacc:print(ClientMsacc), + msacc:print(ServerMsacc), + ct:pal("Got ~p busy_dist_port msgs",[length(Msgs)]), + ct:log("Stats of B from A: ~p", + [ssl_apply(HA, net_kernel, node_info, [B])]), + ct:log("Stats of A from B: ~p", + [ssl_apply(HB, net_kernel, node_info, [A])]), + SchedUtilClient = + round(10000 * msacc:stats(system_runtime,ClientMsacc) / + msacc:stats(system_realtime,ClientMsacc)), + SchedUtilServer = + round(10000 * msacc:stats(system_runtime,ServerMsacc) / + msacc:stats(system_realtime,ServerMsacc)), + Verdict = + case Msgs of + [] -> + ""; + _ -> + " ???" + end, + {comment, ClientComment} = + report(Prefix ++ " Sched Utilization Client" ++ Verdict, + SchedUtilClient, "/100 %" ++ Verdict), + {comment, ServerComment} = + report(Prefix++" Sched Utilization Server" ++ Verdict, + SchedUtilServer, "/100 %" ++ Verdict), + {comment, "Client " ++ ClientComment ++ ", Server " ++ ServerComment}. + +%% Runs on node A and spawns a server on node B +%% We want to avoid getting busy_dist_port as it hides the true SU usage +%% of the receiver and sender. +sched_util_runner(A, B, true) -> + sched_util_runner(A, B, 250); +sched_util_runner(A, B, false) -> + sched_util_runner(A, B, 250); +sched_util_runner(A, B, Senders) -> + Payload = payload(5), + [A] = rpc:call(B, erlang, nodes, []), + ServerPids = + [erlang:spawn_link( + B, fun () -> throughput_server() end) + || _ <- lists:seq(1, Senders)], + ServerMsacc = + erlang:spawn( + B, + fun() -> + receive + {start,Pid} -> + msacc:start(10000), + receive + {done,Pid} -> + Pid ! {self(),msacc:stats()} + end + end + end), + erlang:system_monitor(self(),[busy_dist_port]), + %% We spawn 250 senders which should mean that we + %% have a load of 250 msgs/msec + [spawn_link( + fun() -> + throughput_client(Pid, Payload) + end) || Pid <- ServerPids], + %% + receive after 1000 -> ok end, + ServerMsacc ! {start,self()}, + msacc:start(10000), + ClientMsaccStats = msacc:stats(), + receive after 1000 -> ok end, + ServerMsacc ! {done,self()}, + ServerMsaccStats = receive {ServerMsacc,Stats} -> Stats end, + %% + {ClientMsaccStats,ServerMsaccStats, flush()}. + +flush() -> + receive + M -> + [M | flush()] + after 0 -> + [] + end. + +throughput_server() -> + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + throughput_server(). + +throughput_client(Pid, Payload) -> + Pid ! Payload, + receive after 1 -> throughput_client(Pid, Payload) end. %%----------------- %% Throughput speed @@ -425,15 +552,20 @@ throughput(A, B, Prefix, HA, HB, Packets, Size) -> + byte_size(erlang:term_to_binary([0|<<>>])), % Benchmark overhead Bytes = Packets * (Size + Overhead), io:format("~w bytes, ~.4g s~n", [Bytes,Time/1000000]), + SizeString = integer_to_list(Size), ClientMsaccStats =:= undefined orelse - io:format( - "Sender core usage ratio: ~.4g ns/byte~n", - [msacc:stats(system_runtime, ClientMsaccStats)*1000/Bytes]), + report( + Prefix ++ " Sender_RelativeCoreLoad_" ++ SizeString, + round(msacc:stats(system_runtime, ClientMsaccStats) + * 1000000 / Bytes), + "ps/byte"), ServerMsaccStats =:= undefined orelse begin - io:format( - "Receiver core usage ratio: ~.4g ns/byte~n", - [msacc:stats(system_runtime, ServerMsaccStats)*1000/Bytes]), + report( + Prefix ++ " Receiver_RelativeCoreLoad_" ++ SizeString, + round(msacc:stats(system_runtime, ServerMsaccStats) + * 1000000 / Bytes), + "ps/byte"), msacc:print(ServerMsaccStats) end, io:format("******* ClientProf:~n", []), prof_print(ClientProf), @@ -441,7 +573,7 @@ throughput(A, B, Prefix, HA, HB, Packets, Size) -> io:format("******* Server GC Before:~n~p~n", [Server_GC_Before]), io:format("******* Server GC After:~n~p~n", [Server_GC_After]), Speed = round((Bytes * 1000000) / (1024 * Time)), - report(Prefix++" Throughput_"++integer_to_list(Size), Speed, "kB/s"). + report(Prefix ++ " Throughput_" ++ SizeString, Speed, "kB/s"). %% Runs on node A and spawns a server on node B throughput_runner(A, B, Rounds, Size) -> @@ -449,11 +581,12 @@ throughput_runner(A, B, Rounds, Size) -> [A] = rpc:call(B, erlang, nodes, []), ClientPid = self(), ServerPid = - erlang:spawn( + erlang:spawn_opt( B, - fun () -> throughput_server(ClientPid, Rounds) end), + fun () -> throughput_server(ClientPid, Rounds) end, + [{message_queue_data, off_heap}]), ServerMon = erlang:monitor(process, ServerPid), - msacc:available() andalso + msacc_available() andalso begin msacc:stop(), msacc:reset(), @@ -465,7 +598,7 @@ throughput_runner(A, B, Rounds, Size) -> throughput_client(ServerPid, ServerMon, Payload, Rounds), prof_stop(), MsaccStats = - case msacc:available() of + case msacc_available() of true -> MStats = msacc:stats(), msacc:stop(), @@ -505,7 +638,7 @@ throughput_server(Pid, N) -> GC_Before = get_server_gc_info(), %% dbg:tracer(port, dbg:trace_port(file, "throughput_server_gc.log")), %% dbg:p(TLSDistReceiver, garbage_collection), - msacc:available() andalso + msacc_available() andalso begin msacc:stop(), msacc:reset(), @@ -518,7 +651,7 @@ throughput_server(Pid, N) -> throughput_server_loop(_Pid, GC_Before, 0) -> prof_stop(), MsaccStats = - case msacc:available() of + case msacc_available() of true -> msacc:stop(), MStats = msacc:stats(), @@ -535,8 +668,13 @@ throughput_server_loop(_Pid, GC_Before, 0) -> server_gc_after => get_server_gc_info()}); throughput_server_loop(Pid, GC_Before, N) -> receive - {Pid, N, _} -> - throughput_server_loop(Pid, GC_Before, N-1) + Msg -> + case Msg of + {Pid, N, _} -> + throughput_server_loop(Pid, GC_Before, N - 1); + Other -> + erlang:error({self(),?FUNCTION_NAME,Other}) + end end. get_server_gc_info() -> @@ -676,7 +814,7 @@ get_node_args(Tag, Config) -> true -> proplists:get_value(Tag, Config); false -> - "" + proplists:get_value(ssl_dist_args, Config, "") end. @@ -731,3 +869,6 @@ report(Name, Value, Unit) -> term_to_string(Term) -> unicode:characters_to_list( io_lib:write(Term, [{encoding, unicode}])). + +msacc_available() -> + msacc:available(). diff --git a/lib/ssl/test/ssl_dist_test_lib.erl b/lib/ssl/test/ssl_dist_test_lib.erl index 1b9c853fc4..b8b805b2c4 100644 --- a/lib/ssl/test/ssl_dist_test_lib.erl +++ b/lib/ssl/test/ssl_dist_test_lib.erl @@ -144,6 +144,8 @@ mk_node_cmdline(ListenPort, Name, Args) -> ++ integer_to_list(ListenPort) ++ " " ++ Args ++ " " ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ Name ++ " " + ++ "-kernel inet_dist_connect_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" " + ++ "-kernel inet_dist_listen_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" " ++ "-kernel error_logger \"{file,\\\"" ++ Pwd ++ "/error_log." ++ Name ++ "\\\"}\" " ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()). @@ -179,8 +181,9 @@ check_ssl_node_up(Socket, Name, Bin) -> Parent = self(), Go = make_ref(), %% Spawn connection handler on test server side - Pid = spawn_link( + Pid = spawn( fun () -> + link(group_leader()), receive Go -> ok end, process_flag(trap_exit, true), tstsrvr_con_loop(Name, Socket, Parent) diff --git a/lib/ssl/test/ssl_eqc_SUITE.erl b/lib/ssl/test/ssl_eqc_SUITE.erl new file mode 100644 index 0000000000..bfdb1a3d54 --- /dev/null +++ b/lib/ssl/test/ssl_eqc_SUITE.erl @@ -0,0 +1,70 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2015. 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_eqc_SUITE). + +-compile(export_all). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + tls_handshake_encoding, + tls_cipher_suite_names, + tls_cipher_openssl_suite_names + ]. + +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + ct_property_test:init_per_suite(Config). +end_per_suite(Config) -> + Config. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_,Config) -> + Config. + +init_per_testcase(_, Config0) -> + Config0. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +tls_handshake_encoding(Config) when is_list(Config) -> + %% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_hs_encode_decode()). + true = ct_property_test:quickcheck(ssl_eqc_handshake:prop_tls_hs_encode_decode(), + Config). + +tls_cipher_suite_names(Config) when is_list(Config) -> + %% manual test: proper:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_rfc_name()). + true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_rfc_name(), + Config). + +tls_cipher_openssl_suite_names(Config) when is_list(Config) -> + %% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_cipher_suite_openssl_name()). + true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_openssl_name(), + Config). diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index 1fa6029963..2750a4a9dc 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -25,6 +25,7 @@ -compile(export_all). -include_lib("common_test/include/ct.hrl"). +-include("ssl_alert.hrl"). -include("ssl_handshake.hrl"). -include("ssl_internal.hrl"). -include("tls_handshake.hrl"). @@ -42,7 +43,7 @@ all() -> [decode_hello_handshake, decode_empty_server_sni_correctly, select_proper_tls_1_2_rsa_default_hashsign, ignore_hassign_extension_pre_tls_1_2, - unorded_chain, + unorded_chain, signature_algorithms, encode_decode_srp]. %%-------------------------------------------------------------------- @@ -57,7 +58,9 @@ init_per_group(_GroupName, Config) -> end_per_group(_,Config) -> Config. -init_per_testcase(ignore_hassign_extension_pre_tls_1_2, Config0) -> +init_per_testcase(TC, Config0) when + TC =:= ignore_hassign_extension_pre_tls_1_2 orelse + TC =:= signature_algorithms -> catch crypto:stop(), try crypto:start() of ok -> @@ -106,15 +109,13 @@ decode_hello_handshake(_Config) -> #ssl_options{}), {Hello, _Data} = hd(Records), - #renegotiation_info{renegotiated_connection = <<0>>} - = (Hello#server_hello.extensions)#hello_extensions.renegotiation_info. - + Extensions = Hello#server_hello.extensions, + #{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions. decode_single_hello_extension_correctly(_Config) -> Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>, - Extensions = ssl_handshake:decode_hello_extensions(Renegotiation), - #renegotiation_info{renegotiated_connection = <<0>>} - = Extensions#hello_extensions.renegotiation_info. + Extensions = ssl_handshake:decode_extensions(Renegotiation, {3,3}, undefined), + #{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions. decode_supported_elliptic_curves_hello_extension_correctly(_Config) -> % List of supported and unsupported curves (RFC4492:S5.1.1) @@ -125,37 +126,34 @@ decode_supported_elliptic_curves_hello_extension_correctly(_Config) -> Len = ListLen + 2, Extension = <<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), ?UINT16(ListLen), EllipticCurveList/binary>>, % after decoding we should see only valid curves - #hello_extensions{elliptic_curves = DecodedCurves} = ssl_handshake:decode_hello_extensions(Extension), - #elliptic_curves{elliptic_curve_list = [?sect233k1, ?sect193r2]} = DecodedCurves. + Extensions = ssl_handshake:decode_hello_extensions(Extension, {3,2}, {3,2}, client), + #{elliptic_curves := #elliptic_curves{elliptic_curve_list = [?sect233k1, ?sect193r2]}} = Extensions. decode_unknown_hello_extension_correctly(_Config) -> FourByteUnknown = <<16#CA,16#FE, ?UINT16(4), 3, 0, 1, 2>>, Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>, - Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>), - #renegotiation_info{renegotiated_connection = <<0>>} - = Extensions#hello_extensions.renegotiation_info. + Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, {3,2}, {3,2}, client), + #{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions. + encode_single_hello_sni_extension_correctly(_Config) -> - Exts = #hello_extensions{sni = #sni{hostname = "test.com"}}, SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08, $t, $e, $s, $t, $., $c, $o, $m>>, ExtSize = byte_size(SNI), HelloExt = <<ExtSize:16/unsigned-big-integer, SNI/binary>>, - Encoded = ssl_handshake:encode_hello_extensions(Exts), + Encoded = ssl_handshake:encode_extensions([#sni{hostname = "test.com"}]), HelloExt = Encoded. decode_single_hello_sni_extension_correctly(_Config) -> - Exts = #hello_extensions{sni = #sni{hostname = "test.com"}}, SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08, $t, $e, $s, $t, $., $c, $o, $m>>, - Decoded = ssl_handshake:decode_hello_extensions(SNI), - Exts = Decoded. + Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, client), + #{sni := #sni{hostname = "test.com"}} = Decoded. decode_empty_server_sni_correctly(_Config) -> - Exts = #hello_extensions{sni = #sni{hostname = ""}}, SNI = <<?UINT16(?SNI_EXT),?UINT16(0)>>, - Decoded = ssl_handshake:decode_hello_extensions(SNI), - Exts = Decoded. + Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, server), + #{sni := #sni{hostname = ""}} = Decoded. select_proper_tls_1_2_rsa_default_hashsign(_Config) -> @@ -170,11 +168,11 @@ ignore_hassign_extension_pre_tls_1_2(Config) -> Opts = proplists:get_value(server_opts, Config), CertFile = proplists:get_value(certfile, Opts), [{_, Cert, _}] = ssl_test_lib:pem_to_der(CertFile), - HashSigns = #hash_sign_algos{hash_sign_algos = [{sha512, rsa}, {sha, dsa}]}, - {sha512, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,3}), {3,3}), + HashSigns = #hash_sign_algos{hash_sign_algos = [{sha512, rsa}, {sha, dsa}, {sha, rsa}]}, + {sha512, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,3}), {3,3}), %%% Ignore - {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,2}), {3,2}), - {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,0}), {3,0}). + {md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,2}), {3,2}), + {md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,0}), {3,0}). unorded_chain(Config) when is_list(Config) -> DefConf = ssl_test_lib:default_cert_chain_conf(), @@ -195,30 +193,78 @@ unorded_chain(Config) when is_list(Config) -> ssl_certificate:certificate_chain(PeerCert, ets:new(foo, []), ExtractedCerts, UnordedChain). encode_decode_srp(_Config) -> - Exts = #hello_extensions{ - srp = #srp{username = <<"foo">>}, - sni = #sni{hostname = "bar"}, - renegotiation_info = undefined, - signature_algs = undefined, - alpn = undefined, - next_protocol_negotiation = undefined, - ec_point_formats = undefined, - elliptic_curves = undefined - }, - EncodedExts = <<0,20, % Length + Exts = #{srp => #srp{username = <<"foo">>}, + sni => #sni{hostname = "bar"}, + renegotiation_info => undefined, + signature_algs => undefined, + alpn => undefined, + next_protocol_negotiation => undefined, + ec_point_formats => undefined, + elliptic_curves => undefined + }, + EncodedExts0 = <<0,20, % Length + 0,12, % SRP extension + 0,4, % Length + 3, % srp_I length + 102,111,111, % username = "foo" 0,0, % SNI extension 0,8, % Length 0,6, % ServerNameLength 0, % NameType (host_name) 0,3, % HostNameLength - 98,97,114, % hostname = "bar" - 0,12, % SRP extension - 0,4, % Length - 3, % srp_I length - 102,111,111>>, % username = "foo" - EncodedExts = ssl_handshake:encode_hello_extensions(Exts), - Exts = ssl_handshake:decode_hello_extensions({client, EncodedExts}). + 98,97,114>>, % hostname = "bar" + EncodedExts0 = <<?UINT16(_),EncodedExts/binary>> = + ssl_handshake:encode_hello_extensions(Exts, {3,3}), + Exts = ssl_handshake:decode_hello_extensions(EncodedExts, {3,3}, {3,3}, client). +signature_algorithms(Config) -> + Opts = proplists:get_value(server_opts, Config), + CertFile = proplists:get_value(certfile, Opts), + io:format("Cert = ~p~n", [CertFile]), + [{_, Cert, _}] = ssl_test_lib:pem_to_der(CertFile), + HashSigns0 = #hash_sign_algos{ + hash_sign_algos = [{sha512, rsa}, + {sha, dsa}, + {sha, rsa}]}, + Schemes0 = #signature_algorithms_cert{ + signature_scheme_list = [rsa_pkcs1_sha1, + ecdsa_sha1]}, + {sha512, rsa} = ssl_handshake:select_hashsign( + {HashSigns0, Schemes0}, + Cert, ecdhe_rsa, + tls_v1:default_signature_algs({3,3}), + {3,3}), + HashSigns1 = #hash_sign_algos{ + hash_sign_algos = [{sha, dsa}, + {sha, rsa}]}, + {sha, rsa} = ssl_handshake:select_hashsign( + {HashSigns1, Schemes0}, + Cert, ecdhe_rsa, + tls_v1:default_signature_algs({3,3}), + {3,3}), + Schemes1 = #signature_algorithms_cert{ + signature_scheme_list = [rsa_pkcs1_sha256, + ecdsa_sha1]}, + %% Signature not supported + #alert{} = ssl_handshake:select_hashsign( + {HashSigns1, Schemes1}, + Cert, ecdhe_rsa, + tls_v1:default_signature_algs({3,3}), + {3,3}), + %% No scheme, hashsign is used + {sha, rsa} = ssl_handshake:select_hashsign( + {HashSigns1, undefined}, + Cert, ecdhe_rsa, + tls_v1:default_signature_algs({3,3}), + {3,3}), + HashSigns2 = #hash_sign_algos{ + hash_sign_algos = [{sha, dsa}]}, + %% Signature not supported + #alert{} = ssl_handshake:select_hashsign( + {HashSigns2, Schemes1}, + Cert, ecdhe_rsa, + tls_v1:default_signature_algs({3,3}), + {3,3}). %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl index 878e983bb9..8a76a3a82b 100644 --- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_npn_SUITE.erl @@ -19,7 +19,7 @@ %% %% --module(ssl_npn_handshake_SUITE). +-module(ssl_npn_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl index 35af666e9e..7dbc0c5134 100644 --- a/lib/ssl/test/ssl_npn_hello_SUITE.erl +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -24,10 +24,11 @@ %% Note: This directive should only be used in test suites. -compile(export_all). --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("tls_handshake.hrl"). --include("tls_record.hrl"). + +-include_lib("ssl/src/tls_record.hrl"). +-include_lib("ssl/src/tls_handshake.hrl"). +-include_lib("ssl/src/ssl_cipher.hrl"). +-include_lib("ssl/src/ssl_internal.hrl"). -include_lib("common_test/include/ct.hrl"). %%-------------------------------------------------------------------- @@ -71,44 +72,46 @@ encode_and_decode_client_hello_test(Config) -> Version = ssl_test_lib:protocol_version(Config), {[{DecodedHandshakeMessage, _Raw}], _} = tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}), - NextProtocolNegotiation = (DecodedHandshakeMessage#client_hello.extensions)#hello_extensions.next_protocol_negotiation, - NextProtocolNegotiation = undefined. + Extensions = DecodedHandshakeMessage#client_hello.extensions, + #{next_protocol_negotiation := undefined} = Extensions. %%-------------------------------------------------------------------- encode_and_decode_npn_client_hello_test(Config) -> HandShakeData = create_client_handshake(#next_protocol_negotiation{extension_data = <<>>}), Version = ssl_test_lib:protocol_version(Config), {[{DecodedHandshakeMessage, _Raw}], _} = tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}), - NextProtocolNegotiation = (DecodedHandshakeMessage#client_hello.extensions)#hello_extensions.next_protocol_negotiation, - NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<>>}. + Extensions = DecodedHandshakeMessage#client_hello.extensions, + #{next_protocol_negotiation := #next_protocol_negotiation{extension_data = <<>>}} = Extensions. %%-------------------------------------------------------------------- encode_and_decode_server_hello_test(Config) -> HandShakeData = create_server_handshake(undefined), Version = ssl_test_lib:protocol_version(Config), {[{DecodedHandshakeMessage, _Raw}], _} = tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}), - NextProtocolNegotiation = (DecodedHandshakeMessage#server_hello.extensions)#hello_extensions.next_protocol_negotiation, - NextProtocolNegotiation = undefined. + Extensions = DecodedHandshakeMessage#server_hello.extensions, + #{next_protocol_negotiation := undefined} = Extensions. + %%-------------------------------------------------------------------- encode_and_decode_npn_server_hello_test(Config) -> HandShakeData = create_server_handshake(#next_protocol_negotiation{extension_data = <<6, "spdy/2">>}), Version = ssl_test_lib:protocol_version(Config), {[{DecodedHandshakeMessage, _Raw}], _} = tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}), - NextProtocolNegotiation = (DecodedHandshakeMessage#server_hello.extensions)#hello_extensions.next_protocol_negotiation, - ct:log("~p ~n", [NextProtocolNegotiation]), - NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<6, "spdy/2">>}. + Extensions = DecodedHandshakeMessage#server_hello.extensions, + ct:log("~p ~n", [Extensions]), + #{next_protocol_negotiation := #next_protocol_negotiation{extension_data = <<6, "spdy/2">>}} = Extensions. %%-------------------------------------------------------------------- create_server_hello_with_no_advertised_protocols_test(_Config) -> - Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), #hello_extensions{}), - undefined = (Hello#server_hello.extensions)#hello_extensions.next_protocol_negotiation. + Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), #{}), + Extensions = Hello#server_hello.extensions, + #{} = Extensions. %%-------------------------------------------------------------------- create_server_hello_with_advertised_protocols_test(_Config) -> Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), - #hello_extensions{next_protocol_negotiation = [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]}), - [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>] = - (Hello#server_hello.extensions)#hello_extensions.next_protocol_negotiation. + #{next_protocol_negotiation => [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]}), + Extensions = Hello#server_hello.extensions, + #{next_protocol_negotiation := [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]} = Extensions. %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- @@ -120,9 +123,8 @@ create_client_handshake(Npn) -> session_id = <<>>, cipher_suites = [?TLS_DHE_DSS_WITH_DES_CBC_SHA], compression_methods = "", - extensions = #hello_extensions{ - next_protocol_negotiation = Npn, - renegotiation_info = #renegotiation_info{}} + extensions = #{next_protocol_negotiation => Npn, + renegotiation_info => #renegotiation_info{}} }, Vsn). create_server_handshake(Npn) -> @@ -133,9 +135,8 @@ create_server_handshake(Npn) -> session_id = <<>>, cipher_suite = ?TLS_DHE_DSS_WITH_DES_CBC_SHA, compression_method = 1, - extensions = #hello_extensions{ - next_protocol_negotiation = Npn, - renegotiation_info = #renegotiation_info{}} + extensions = #{next_protocol_negotiation => Npn, + renegotiation_info => #renegotiation_info{}} }, Vsn). create_connection_states() -> @@ -146,5 +147,5 @@ create_connection_states() -> } }, current_read => #{secure_renegotiation => false - } + } }. diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index 27b9c258a0..2d0ffd03d7 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -48,21 +48,27 @@ groups() -> payload_tests() -> [server_echos_passive_small, + server_echos_passive_chunk_small, server_echos_active_once_small, server_echos_active_small, client_echos_passive_small, + client_echos_passive_chunk_small, client_echos_active_once_small, client_echos_active_small, server_echos_passive_big, + server_echos_passive_chunk_big, server_echos_active_once_big, server_echos_active_big, client_echos_passive_big, + client_echos_passive_chunk_big, client_echos_active_once_big, client_echos_active_big, server_echos_passive_huge, + server_echos_passive_chunk_huge, server_echos_active_once_huge, server_echos_active_huge, client_echos_passive_huge, + client_echos_passive_chunk_huge, client_echos_active_once_huge, client_echos_active_huge, client_active_once_server_close]. @@ -109,9 +115,11 @@ end_per_group(GroupName, Config) -> init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_huge; + TestCase == server_echos_passive_chunk_huge; TestCase == server_echos_active_once_huge; TestCase == server_echos_active_huge; TestCase == client_echos_passive_huge; + TestCase == client_echos_passive_chunk_huge; TestCase == client_echos_active_once_huge; TestCase == client_echos_active_huge -> case erlang:system_info(system_architecture) of @@ -124,9 +132,11 @@ init_per_testcase(TestCase, Config) init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_big; + TestCase == server_echos_passive_chunk_big; TestCase == server_echos_active_once_big; TestCase == server_echos_active_big; TestCase == client_echos_passive_big; + TestCase == client_echos_passive_chunk_big; TestCase == client_echos_active_once_big; TestCase == client_echos_active_big -> ct:timetrap({seconds, 60}), @@ -157,6 +167,22 @@ server_echos_passive_small(Config) when is_list(Config) -> %%-------------------------------------------------------------------- +server_echos_passive_chunk_small() -> + [{doc, "Client sends 1000 bytes in passive mode to server, that receives them in chunks, " + "sends them back, and closes."}]. + +server_echos_passive_chunk_small(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 100), + server_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + + +%%-------------------------------------------------------------------- + server_echos_active_once_small() -> [{doc, "Client sends 1000 bytes in active once mode to server, that receives " " them, sends them back, and closes."}]. @@ -200,6 +226,21 @@ client_echos_passive_small(Config) when is_list(Config) -> Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- +client_echos_passive_chunk__small() -> + [{doc, "Server sends 1000 bytes in passive mode to client, that receives them in chunks, " + "sends them back, and closes."}]. + +client_echos_passive_chunk_small(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 100), + client_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + + +%%-------------------------------------------------------------------- client_echos_active_once_small() -> ["Server sends 1000 bytes in active once mode to client, that receives " "them, sends them back, and closes."]. @@ -241,6 +282,19 @@ server_echos_passive_big(Config) when is_list(Config) -> Data = binary:copy(<<"1234567890">>, 5000), server_echos_passive( Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). +%%-------------------------------------------------------------------- +server_echos_passive_chunk_big() -> + [{doc, "Client sends 50000 bytes to server in passive mode, that receives them, " + "sends them back, and closes."}]. + +server_echos_passive_chunk_big(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 5000), + server_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- @@ -286,6 +340,22 @@ client_echos_passive_big(Config) when is_list(Config) -> client_echos_passive( Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + +%%-------------------------------------------------------------------- +client_echos_passive_chunk_big() -> + [{doc, "Server sends 50000 bytes to client in passive mode, that receives them, " + "sends them back, and closes."}]. + +client_echos_passive_chunk_big(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 5000), + client_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + + %%-------------------------------------------------------------------- client_echos_active_once_big() -> [{doc, "Server sends 50000 bytes to client in active once mode, that receives" @@ -329,6 +399,20 @@ server_echos_passive_huge(Config) when is_list(Config) -> Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- +server_echos_passive_chunk_huge() -> + [{doc, "Client sends 500000 bytes to server in passive mode, that receives " + " them, sends them back, and closes."}]. + +server_echos_passive_chunk_huge(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 50000), + server_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + +%%-------------------------------------------------------------------- server_echos_active_once_huge() -> [{doc, "Client sends 500000 bytes to server in active once mode, that receives " "them, sends them back, and closes."}]. @@ -369,7 +453,19 @@ client_echos_passive_huge(Config) when is_list(Config) -> Data = binary:copy(<<"1234567890">>, 50000), client_echos_passive( Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). +%%-------------------------------------------------------------------- +client_echos_passive_chunk_huge() -> + [{doc, "Server sends 500000 bytes to client in passive mode, that receives " + "them, sends them back, and closes."}]. +client_echos_passive_chunk_huge(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 50000), + client_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_active_once_huge() -> [{doc, "Server sends 500000 bytes to client in active once mode, that receives " @@ -442,6 +538,28 @@ server_echos_passive( ssl_test_lib:close(Server), ssl_test_lib:close(Client). +server_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, echoer_chunk, [Length]}}, + {options, [{active, false}, {mode, binary} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, sender, [Data]}}, + {options, [{active, false}, {mode, binary} | ClientOpts]}]), + %% + ssl_test_lib:check_result(Server, ok, Client, ok), + %% + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). server_echos_active_once( Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> @@ -513,6 +631,31 @@ client_echos_passive( ssl_test_lib:close(Server), ssl_test_lib:close(Client). + +client_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, sender, [Data]}}, + {options, [{active, false}, {mode, binary} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, echoer_chunk, [Length]}}, + {options, [{active, false}, {mode, binary} | ClientOpts]}]), + %% + ssl_test_lib:check_result(Server, ok, Client, ok), + %% + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + client_echos_active_once( Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> Length = byte_size(Data), @@ -615,6 +758,10 @@ echoer(Socket, Size) -> ct:log("Echoer recv: ~p~n", [ssl:getopts(Socket, [active])]), echo_recv(Socket, Size * 100). +echoer_chunk(Socket, Size) -> + ct:log("Echoer recv: ~p~n", [ssl:getopts(Socket, [active])]), + echo_recv_chunk(Socket, Size, Size * 100). + echoer_active_once(Socket, Size) -> ct:log("Echoer active once: ~p~n", [ssl:getopts(Socket, [active])]), echo_active_once(Socket, Size * 100). @@ -632,6 +779,16 @@ echo_recv(Socket, Size) -> ok = ssl:send(Socket, Data), echo_recv(Socket, Size - byte_size(Data)). + +%% Receive Size bytes +echo_recv_chunk(_Socket, _, 0) -> + ok; +echo_recv_chunk(Socket, ChunkSize, Size) -> + {ok, Data} = ssl:recv(Socket, ChunkSize), + ok = ssl:send(Socket, Data), + echo_recv_chunk(Socket, ChunkSize, Size - ChunkSize). + + %% Receive Size bytes echo_active_once(_Socket, 0) -> ok; diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl index 6f11e2bbe8..3c7f6ab20f 100644 --- a/lib/ssl/test/ssl_pem_cache_SUITE.erl +++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl @@ -34,7 +34,10 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- all() -> - [pem_cleanup, invalid_insert]. + [ + pem_cleanup, + clear_pem_cache, + invalid_insert]. groups() -> []. @@ -110,6 +113,37 @@ pem_cleanup(Config)when is_list(Config) -> ssl_test_lib:close(Client), false = Size == Size1. +clear_pem_cache() -> + [{doc,"Test that internal reference tabel is cleaned properly even when " + " the PEM cache is cleared" }]. +clear_pem_cache(Config) when is_list(Config) -> + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + [_,{FilRefDb, _} |_] = element(6, State), + {Server, Client} = basic_verify_test_no_close(Config), + CountReferencedFiles = fun({_, -1}, Acc) -> + Acc; + ({_, N}, Acc) -> + N + Acc + end, + + 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), + ssl:clear_pem_cache(), + _ = sys:get_status(whereis(ssl_manager)), + {Server1, Client1} = basic_verify_test_no_close(Config), + 4 = ets:foldl(CountReferencedFiles, 0, FilRefDb), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ct:sleep(2000), + _ = sys:get_status(whereis(ssl_manager)), + 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1), + ct:sleep(2000), + _ = sys:get_status(whereis(ssl_manager)), + 0 = ets:foldl(CountReferencedFiles, 0, FilRefDb). + invalid_insert() -> [{doc, "Test that insert of invalid pem does not cause empty cache entry"}]. invalid_insert(Config)when is_list(Config) -> @@ -163,3 +197,22 @@ later()-> Gregorian = calendar:datetime_to_gregorian_seconds(DateTime), calendar:gregorian_seconds_to_datetime(Gregorian + (2 * ?CLEANUP_INTERVAL)). +basic_verify_test_no_close(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + {Server, Client}. diff --git a/lib/ssl/test/ssl_renegotiate_SUITE.erl b/lib/ssl/test/ssl_renegotiate_SUITE.erl new file mode 100644 index 0000000000..ef3f9ebb52 --- /dev/null +++ b/lib/ssl/test/ssl_renegotiate_SUITE.erl @@ -0,0 +1,499 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_renegotiate_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-define(SLEEP, 500). +-define(RENEGOTIATION_DISABLE_TIME, 12000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [{'dtlsv1.2', [], renegotiate_tests()}, + {'dtlsv1', [], renegotiate_tests()}, + {'tlsv1.3', [], renegotiate_tests()}, + {'tlsv1.2', [], renegotiate_tests()}, + {'tlsv1.1', [], renegotiate_tests()}, + {'tlsv1', [], renegotiate_tests()}, + {'sslv3', [], ssl3_renegotiate_tests()} + ]. + +renegotiate_tests() -> + [client_renegotiate, + server_renegotiate, + client_secure_renegotiate, + client_secure_renegotiate_fallback, + client_renegotiate_reused_session, + server_renegotiate_reused_session, + client_no_wrap_sequence_number, + server_no_wrap_sequence_number, + renegotiate_dos_mitigate_active, + renegotiate_dos_mitigate_passive, + renegotiate_dos_mitigate_absolute]. + +ssl3_renegotiate_tests() -> + [client_renegotiate, + server_renegotiate, + client_renegotiate_reused_session, + server_renegotiate_reused_session, + client_no_wrap_sequence_number, + server_no_wrap_sequence_number, + renegotiate_dos_mitigate_active, + renegotiate_dos_mitigate_passive, + renegotiate_dos_mitigate_absolute]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +client_renegotiate() -> + [{doc,"Test ssl:renegotiate/1 on client."}]. +client_renegotiate(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_secure_renegotiate() -> + [{doc,"Test ssl:renegotiate/1 on client."}]. +client_secure_renegotiate(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{secure_renegotiate, true} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false}, + {secure_renegotiate, true}| ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_secure_renegotiate_fallback() -> + [{doc,"Test that we can set secure_renegotiate to false that is " + "fallback option, we however do not have a insecure server to test against!"}]. +client_secure_renegotiate_fallback(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{secure_renegotiate, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false}, + {secure_renegotiate, false}| ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +server_renegotiate() -> + [{doc,"Test ssl:renegotiate/1 on server."}]. +server_renegotiate(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_renegotiate_reused_session() -> + [{doc,"Test ssl:renegotiate/1 on client when the ssl session will be reused."}]. +client_renegotiate_reused_session(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_reuse_session, [Data]}}, + {options, [{reuse_sessions, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +server_renegotiate_reused_session() -> + [{doc,"Test ssl:renegotiate/1 on server when the ssl session will be reused."}]. +server_renegotiate_reused_session(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_reuse_session, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{reuse_sessions, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +client_no_wrap_sequence_number() -> + [{doc,"Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. + +client_no_wrap_sequence_number(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to erlang", + N = 12, + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Version = ssl_test_lib:protocol_version(Config, tuple), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[ErlData, treashold(N, Version)]]}}, + {options, [{reuse_sessions, false}, + {renegotiate_at, N} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +server_no_wrap_sequence_number() -> + [{doc, "Test that erlang server will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. + +server_no_wrap_sequence_number(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + N = 12, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[Data, N+2]]}}, + {options, [{renegotiate_at, N} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +renegotiate_dos_mitigate_active() -> + [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", + "immediately after each other"}]. +renegotiate_dos_mitigate_active(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_immediately, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +renegotiate_dos_mitigate_passive() -> + [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", + "immediately after each other"}]. +renegotiate_dos_mitigate_passive(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_immediately, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +renegotiate_dos_mitigate_absolute() -> + [{doc, "Mitigate DOS computational attack by not allowing client to initiate renegotiation"}]. +renegotiate_dos_mitigate_absolute(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{client_renegotiation, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_rejected, + []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +renegotiate(Socket, Data) -> + ct:log("Renegotiating ~n", []), + Result = ssl:renegotiate(Socket), + ct:log("Result ~p~n", [Result]), + ssl:send(Socket, Data), + case Result of + ok -> + ok; + Other -> + Other + end. + +renegotiate_reuse_session(Socket, Data) -> + %% Make sure session is registered + ct:sleep(?SLEEP), + renegotiate(Socket, Data). + +renegotiate_immediately(Socket) -> + _ = ssl_test_lib:active_recv(Socket, 11), + ok = ssl:renegotiate(Socket), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:sleep(?RENEGOTIATION_DISABLE_TIME + ?SLEEP), + ok = ssl:renegotiate(Socket), + ct:log("Renegotiated again"), + ssl:send(Socket, "Hello world"), + ok. + +renegotiate_rejected(Socket) -> + _ = ssl_test_lib:active_recv(Socket, 11), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:log("Failed to renegotiate again"), + ssl:send(Socket, "Hello world"), + ok. + +%% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast +treashold(N, {3,0}) -> + (N div 2) + 1; +treashold(N, {3,1}) -> + (N div 2) + 1; +treashold(N, _) -> + N + 1. + +erlang_ssl_receive(Socket, Data) -> + case ssl_test_lib:active_recv(Socket, length(Data)) of + Data -> + ok; + Other -> + ct:fail({{expected, Data}, {got, Other}}) + end. diff --git a/lib/ssl/test/ssl_rfc_5869_SUITE.erl b/lib/ssl/test/ssl_rfc_5869_SUITE.erl new file mode 100644 index 0000000000..8b2d1c2082 --- /dev/null +++ b/lib/ssl/test/ssl_rfc_5869_SUITE.erl @@ -0,0 +1,316 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(ssl_rfc_5869_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [sha_256_basic, + sha_256_long, + sha_256_no_salt, + sha_basic, + sha_long, + sha_no_salt, + sha_default_salt + ]. + +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + application:stop(crypto). + +%%-------------------------------------------------------------------- +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +sha_256_basic() -> + [{doc, "Basic test case with SHA-256"}]. +sha_256_basic(Config) when is_list(Config) -> + %% Hash = SHA-256 + %% IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b (22 octets) + %% salt = 0x000102030405060708090a0b0c (13 octets) + %% info = 0xf0f1f2f3f4f5f6f7f8f9 (10 octets) + %% L = 42 + %% PRK = 0x077709362c2e32df0ddc3f0dc47bba63 + %% 90b6c73bb50f9c3122ec844ad7c2b3e5 (32 octets) + %% OKM = 0x3cb25f25faacd57a90434f64d0362f2a + %% 2d2d0a90cf1a5a4c5db02d56ecc4c5bf + %% 34007208d5b887185865 (42 octets) + IKM = hexstr2bin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), + Salt = hexstr2bin("000102030405060708090a0b0c"), + Info = hexstr2bin("f0f1f2f3f4f5f6f7f8f9"), + PRK = hexstr2bin("077709362c2e32df0ddc3f0dc47bba63" + "90b6c73bb50f9c3122ec844ad7c2b3e5"), + OKM = hexstr2bin("3cb25f25faacd57a90434f64d0362f2a" + "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" + "34007208d5b887185865"), + hkdf_test(sha256, Salt, IKM, PRK, Info, 42, OKM). + +sha_256_long() -> + [{doc, "Test with SHA-256 and longer inputs/outputs"}]. +sha_256_long(Config) when is_list(Config) -> + %% Hash = SHA-256 + %% IKM = 0x000102030405060708090a0b0c0d0e0f + %% 101112131415161718191a1b1c1d1e1f + %% 202122232425262728292a2b2c2d2e2f + %% 303132333435363738393a3b3c3d3e3f + %% 404142434445464748494a4b4c4d4e4f (80 octets) + %% salt = 0x606162636465666768696a6b6c6d6e6f + %% 707172737475767778797a7b7c7d7e7f + %% 808182838485868788898a8b8c8d8e8f + %% 909192939495969798999a9b9c9d9e9f + %% a0a1a2a3a4a5a6a7a8a9aaabacadaeaf (80 octets) + %% info = 0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf + %% c0c1c2c3c4c5c6c7c8c9cacbcccdcecf + %% d0d1d2d3d4d5d6d7d8d9dadbdcdddedf + %% e0e1e2e3e4e5e6e7e8e9eaebecedeeef + %% f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff (80 octets) + %% L = 82 + + %% PRK = 0x06a6b88c5853361a06104c9ceb35b45c + %% ef760014904671014a193f40c15fc244 (32 octets) + %% OKM = 0xb11e398dc80327a1c8e7f78c596a4934 + %% 4f012eda2d4efad8a050cc4c19afa97c + %% 59045a99cac7827271cb41c65e590e09 + %% da3275600c2f09b8367793a9aca3db71 + %% cc30c58179ec3e87c14c01d5c1f3434f + %% 1d87 (82 octets) + IKM = hexstr2bin("000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + ), + Salt = hexstr2bin("606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + ), + Info = hexstr2bin("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" + ), + PRK = hexstr2bin("06a6b88c5853361a06104c9ceb35b45c" + "ef760014904671014a193f40c15fc244"), + OKM = hexstr2bin("b11e398dc80327a1c8e7f78c596a4934" + "4f012eda2d4efad8a050cc4c19afa97c" + "59045a99cac7827271cb41c65e590e09" + "da3275600c2f09b8367793a9aca3db71" + "cc30c58179ec3e87c14c01d5c1f3434f" + "1d87" + ), + hkdf_test(sha256, Salt, IKM, PRK, Info, 82, OKM). +sha_256_no_salt() -> + [{doc, "Test with SHA-256 and zero-length salt/info"}]. +sha_256_no_salt(Config) when is_list(Config) -> + %% Hash = SHA-256 + %% IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b (22 octets) + %% salt = (0 octets) + %% info = (0 octets) + %% L = 42 + + %% PRK = 0x19ef24a32c717b167f33a91d6f648bdf + %% 96596776afdb6377ac434c1c293ccb04 (32 octets) + %% OKM = 0x8da4e775a563c18f715f802a063c5a31 + %% b8a11f5c5ee1879ec3454e5f3c738d2d + %% 9d201395faa4b61a96c8 (42 octets) + IKM = hexstr2bin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), + Salt = <<>>, + Info = <<>>, + PRK = hexstr2bin("19ef24a32c717b167f33a91d6f648bdf" + "96596776afdb6377ac434c1c293ccb04"), + OKM = hexstr2bin("8da4e775a563c18f715f802a063c5a31" + "b8a11f5c5ee1879ec3454e5f3c738d2d" + "9d201395faa4b61a96c8"), + hkdf_test(sha256, Salt, IKM, PRK, Info, 42, OKM). + +sha_basic() -> + [{doc, " Basic test case with SHA-1"}]. +sha_basic(Config) when is_list(Config) -> + %% Hash = SHA-1 + %% IKM = 0x0b0b0b0b0b0b0b0b0b0b0b (11 octets) + %% salt = 0x000102030405060708090a0b0c (13 octets) + %% info = 0xf0f1f2f3f4f5f6f7f8f9 (10 octets) + %% L = 42 + + %% PRK = 0x9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243 (20 octets) + %% OKM = 0x085a01ea1b10f36933068b56efa5ad81 + %% a4f14b822f5b091568a9cdd4f155fda2 + %% c22e422478d305f3f896 (42 octets) + IKM = hexstr2bin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), + Salt = hexstr2bin("000102030405060708090a0b0c"), + Info = hexstr2bin("f0f1f2f3f4f5f6f7f8f9"), + PRK = hexstr2bin("077709362c2e32df0ddc3f0dc47bba63" + "90b6c73bb50f9c3122ec844ad7c2b3e5"), + OKM = hexstr2bin("3cb25f25faacd57a90434f64d0362f2a" + "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" + "34007208d5b887185865"), + hkdf_test(sha256, Salt, IKM, PRK, Info, 42, OKM). + +sha_long() -> + [{doc, "Test with SHA-1 and longer inputs/outputs"}]. +sha_long(Config) when is_list(Config) -> + %% Hash = SHA-1 + %% IKM = 0x000102030405060708090a0b0c0d0e0f + %% 101112131415161718191a1b1c1d1e1f + %% 202122232425262728292a2b2c2d2e2f + %% 303132333435363738393a3b3c3d3e3f + %% 404142434445464748494a4b4c4d4e4f (80 octets) + %% salt = 0x606162636465666768696a6b6c6d6e6f + %% 707172737475767778797a7b7c7d7e7f + %% 808182838485868788898a8b8c8d8e8f + %% 909192939495969798999a9b9c9d9e9f + %% a0a1a2a3a4a5a6a7a8a9aaabacadaeaf (80 octets) + %% info = 0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf + %% c0c1c2c3c4c5c6c7c8c9cacbcccdcecf + %% d0d1d2d3d4d5d6d7d8d9dadbdcdddedf + %% e0e1e2e3e4e5e6e7e8e9eaebecedeeef + %% f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff (80 octets) + %% L = 82 + + %% PRK = 0x8adae09a2a307059478d309b26c4115a224cfaf6 (20 octets) + %% OKM = 0x0bd770a74d1160f7c9f12cd5912a06eb + %% ff6adcae899d92191fe4305673ba2ffe + %% 8fa3f1a4e5ad79f3f334b3b202b2173c + %% 486ea37ce3d397ed034c7f9dfeb15c5e + %% 927336d0441f4c4300e2cff0d0900b52 + %% d3b4 (82 octets) + IKM = hexstr2bin("000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + ), + Salt = hexstr2bin("606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + ), + Info = hexstr2bin("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" + ), + PRK = hexstr2bin("8adae09a2a307059478d309b26c4115a224cfaf6"), + OKM = hexstr2bin("0bd770a74d1160f7c9f12cd5912a06eb" + "ff6adcae899d92191fe4305673ba2ffe" + "8fa3f1a4e5ad79f3f334b3b202b2173c" + "486ea37ce3d397ed034c7f9dfeb15c5e" + "927336d0441f4c4300e2cff0d0900b52" + "d3b4" + ), + hkdf_test(sha, Salt, IKM, PRK, Info, 82, OKM). + +sha_no_salt() -> + [{doc, "Test with SHA-1 and zero-length salt/info"}]. +sha_no_salt(Config) when is_list(Config) -> + %% Hash = SHA-1 + %% IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b (22 octets) + %% salt = (0 octets) + %% info = (0 octets) + %% L = 42 + + %% PRK = 0xda8c8a73c7fa77288ec6f5e7c297786aa0d32d01 (20 octets) + %% OKM = 0x0ac1af7002b3d761d1e55298da9d0506 + %% b9ae52057220a306e07b6b87e8df21d0 + %% ea00033de03984d34918 (42 octets) + IKM = hexstr2bin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), + Salt = <<>>, + Info = <<>>, + PRK = hexstr2bin("da8c8a73c7fa77288ec6f5e7c297786aa0d32d01"), + OKM = hexstr2bin("0ac1af7002b3d761d1e55298da9d0506" + "b9ae52057220a306e07b6b87e8df21d0" + "ea00033de03984d34918"), + hkdf_test(sha, Salt, IKM, PRK, Info, 42, OKM). + + +sha_default_salt() -> + [{doc, "Test with SHA-1, salt not provided (defaults to HashLen zero octets), + zero-length info"}]. +sha_default_salt(Config) when is_list(Config) -> + %% Hash = SHA-1 + %% IKM = 0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c (22 octets) + %% salt = not provided (defaults to HashLen zero octets) + %% info = (0 octets) + %% L = 42 + + %% PRK = 0x2adccada18779e7c2077ad2eb19d3f3e731385dd (20 octets) + %% OKM = 0x2c91117204d745f3500d636a62f64f0a + %% b3bae548aa53d423b0d1f27ebba6f5e5 + %% 673a081d70cce7acfc48 (42 octets) + IKM = hexstr2bin("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"), + Salt = binary:copy(<<0>>, 20), + Info = <<>>, + PRK = hexstr2bin("2adccada18779e7c2077ad2eb19d3f3e731385dd"), + OKM = hexstr2bin("2c91117204d745f3500d636a62f64f0a" + "b3bae548aa53d423b0d1f27ebba6f5e5" + "673a081d70cce7acfc48"), + hkdf_test(sha, Salt, IKM, PRK, Info, 42, OKM). + +hkdf_test(HashAlg, Salt, KeyingMaterial, PsedoRandKey, ContextInfo, Length, Key) -> + PsedoRandKey = tls_v1:hkdf_extract(HashAlg, Salt, KeyingMaterial), + Key = tls_v1:hkdf_expand(PsedoRandKey, ContextInfo, Length, HashAlg). + +hexstr2bin(S) when is_binary(S) -> + list_to_binary(hexstr2list(binary_to_list(S))); +hexstr2bin(S) -> + list_to_binary(hexstr2list(S)). + +hexstr2list([$ |T]) -> + hexstr2list(T); +hexstr2list([X,Y|T]) -> + [mkint(X)*16 + mkint(Y) | hexstr2list(T)]; +hexstr2list([]) -> + []. +mkint(C) when $0 =< C, C =< $9 -> + C - $0; +mkint(C) when $A =< C, C =< $F -> + C - $A + 10; +mkint(C) when $a =< C, C =< $f -> + C - $a + 10. diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl new file mode 100644 index 0000000000..aa79698a72 --- /dev/null +++ b/lib/ssl/test/ssl_session_SUITE.erl @@ -0,0 +1,377 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(ssl_session_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include("tls_handshake.hrl"). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-define(SLEEP, 500). +-define(EXPIRE, 10). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [{'dtlsv1.2', [], session_tests()}, + {'dtlsv1', [], session_tests()}, + {'tlsv1.3', [], session_tests()}, + {'tlsv1.2', [], session_tests()}, + {'tlsv1.1', [], session_tests()}, + {'tlsv1', [], session_tests()}, + {'sslv3', [], session_tests()} + ]. + +session_tests() -> + [reuse_session, + reuse_session_expired, + server_does_not_want_to_reuse_session, + no_reuses_session_server_restart_new_cert, + no_reuses_session_server_restart_new_cert_file]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config = ssl_test_lib:make_rsa_cert(Config0), + ssl_test_lib:make_dsa_cert(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(reuse_session_expired, Config) -> + ssl:stop(), + application:load(ssl), + ssl_test_lib:clean_env(), + application:set_env(ssl, session_lifetime, ?EXPIRE), + application:set_env(ssl, session_delay_cleanup_time, 500), + ssl:start(), + ct:timetrap({seconds, 30}), + Config; +init_per_testcase(_, Config) -> + ct:timetrap({seconds, 15}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +reuse_session() -> + [{doc,"Test reuse of sessions (short handshake)"}]. +reuse_session(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +reuse_session_expired() -> + [{doc,"Test sessions is not reused when it has expired"}]. +reuse_session_expired(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server0 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port0 = ssl_test_lib:inet_port(Server0), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), + Server0 ! listen, + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + + SID = receive + {Client0, Id0} -> + Id0 + end, + + receive + {Client1, SID} -> + ok + after ?SLEEP -> + ct:fail(session_not_reused) + end, + + Server0 ! listen, + + %% Make sure session is unregistered due to expiration + ct:sleep((?EXPIRE*2)), + + make_sure_expired(Hostname, Port0, SID), + + Client2 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client2, SID} -> + ct:fail(session_reused_when_session_expired); + {Client2, _} -> + ok + end, + process_flag(trap_exit, false), + ssl_test_lib:close(Server0), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + ssl_test_lib:close(Client2). + +make_sure_expired(Host, Port, Id) -> + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + ClientCache = element(2, State), + + case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of + undefined -> + ok; + #session{is_resumable = false} -> + ok; + _ -> + ct:sleep(?SLEEP), + make_sure_expired(Host, Port, Id) + end. + +%%-------------------------------------------------------------------- +server_does_not_want_to_reuse_session() -> + [{doc,"Test reuse of sessions (short handshake)"}]. +server_does_not_want_to_reuse_session(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, [{reuse_session, fun(_,_,_,_) -> + false + end} | + ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + + %% Make sure session is registered + ct:sleep(?SLEEP), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client1, SessionInfo} -> + ct:fail(session_reused_when_server_does_not_want_to); + {Client1, _Other} -> + ok + end, + ssl_test_lib:close(Client0), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client1). + +no_reuses_session_server_restart_new_cert() -> + [{doc,"Check that a session is not reused if the server is restarted with a new cert."}]. +no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> + + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), + DsaClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + %% Make sure session is registered + ct:sleep(?SLEEP), + Monitor = erlang:monitor(process, Server), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client0), + receive + {'DOWN', Monitor, _, _, _} -> + ok + end, + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{reuseaddr, true} | DsaServerOpts]}]), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, DsaClientOpts}]), + receive + {Client1, SessionInfo} -> + ct:fail(session_reused_when_server_has_new_cert); + {Client1, _Other} -> + ok + end, + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- +no_reuses_session_server_restart_new_cert_file() -> + [{doc,"Check that a session is not reused if a server is restarted with a new " + "cert contained in a file with the same name as the old cert."}]. + +no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + + NewServerOpts0 = ssl_test_lib:new_config(PrivDir, ServerOpts), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, NewServerOpts0}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + %% Make sure session is registered and we get + %% new file time stamp when calling new_config! + ct:sleep(?SLEEP* 2), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client0), + + ssl:clear_pem_cache(), + + NewServerOpts1 = ssl_test_lib:new_config(PrivDir, DsaServerOpts), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{reuseaddr, true} | NewServerOpts1]}]), + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client1, SessionInfo} -> + ct:fail(session_reused_when_server_has_new_cert); + {Client1, _Other} -> + ok + end, + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index 7f33fe3204..b71b15b028 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -186,7 +186,7 @@ session_cleanup() -> session_cleanup(Config) when is_list(Config) -> process_flag(trap_exit, true), ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index 7629d75100..e68ea2c99d 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -36,7 +36,6 @@ all() -> [{group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, - {group, 'sslv3'}, {group, 'dtlsv1.2'}, {group, 'dtlsv1'} ]. @@ -46,7 +45,6 @@ groups() -> {'tlsv1.2', [], sni_tests()}, {'tlsv1.1', [], sni_tests()}, {'tlsv1', [], sni_tests()}, - {'sslv3', [], sni_tests()}, {'dtlsv1.2', [], sni_tests()}, {'dtlsv1', [], sni_tests()} ]. @@ -61,7 +59,8 @@ sni_tests() -> dns_name, ip_fallback, no_ip_fallback, - dns_name_reuse]. + dns_name_reuse, + customize_hostname_check]. init_per_suite(Config0) -> catch crypto:stop(), @@ -88,12 +87,10 @@ end_per_suite(_) -> ssl:stop(), application:stop(crypto). -init_per_testcase(TestCase, Config) when TestCase == ip_fallback; - TestCase == no_ip_fallback; - TestCase == dns_name_reuse -> +init_per_testcase(customize_hostname_check, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), - ct:timetrap({seconds, 20}), + ssl_test_lib:clean_start(), + ct:timetrap({seconds, 5}), Config; init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), @@ -236,7 +233,60 @@ dns_name_reuse(Config) -> {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, [{verify, verify_peer} | ClientConf]}]), - ssl_test_lib:check_client_alert(Client1, handshake_failure). + ssl_test_lib:check_client_alert(Client1, handshake_failure), + ssl_test_lib:close(Client0). + + +customize_hostname_check() -> + [{doc,"Test option customize_hostname_check."}]. +customize_hostname_check(Config) when is_list(Config) -> + Ext = [#'Extension'{extnID = ?'id-ce-subjectAltName', + extnValue = [{dNSName, "*.example.org"}], + critical = false} + ], + #{server_config := ServerOpts0, + client_config := ClientOpts0} = ssl_test_lib:make_cert_chains_pem(rsa, [{server_chain, + [[], + [], + [{extensions, Ext}] + ]}], + Config, "https_hostname_convention"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + CustomFun = public_key:pkix_verify_hostname_match_fun(https), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, + [{verify, verify_peer}, + {server_name_indication, "other.example.org"}, + {customize_hostname_check, + [{match_fun, CustomFun}]} | ClientOpts] + }]), + ssl_test_lib:check_result(Server, ok, Client, ok), + + Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + + Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{verify, verify_peer}, + {server_name_indication, "other.example.org"} | ClientOpts]} + ]), + ssl_test_lib:check_client_alert(Server, Client1, handshake_failure). %%-------------------------------------------------------------------- %% Internal Functions ------------------------------------------------ diff --git a/lib/ssl/test/ssl_socket_SUITE.erl b/lib/ssl/test/ssl_socket_SUITE.erl new file mode 100644 index 0000000000..d648f2f9e1 --- /dev/null +++ b/lib/ssl/test/ssl_socket_SUITE.erl @@ -0,0 +1,437 @@ +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_socket_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-define(SLEEP, 500). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, tls}, + {group, dtls} + ]. + +groups() -> + [ + {tls,[], socket_tests() ++ raw_inet_opt()}, + {dtls,[], socket_tests()} + ]. + +socket_tests() -> + [ + getstat, + socket_options, + invalid_inet_get_option, + invalid_inet_get_option_not_list, + invalid_inet_get_option_improper_list, + invalid_inet_set_option, + invalid_inet_set_option_not_list, + invalid_inet_set_option_improper_list + ]. + +raw_inet_opt() -> + [ + raw_inet_option + ]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(dtls, Config) -> + [{protocol_opts, [{protocol, dtls}]} | proplists:delete(protocol_opts, Config)]; +init_per_group(tls, Config) -> + [{protocol_opts, [{protocol, tls}]} | proplists:delete(protocol_opts, Config)]; +init_per_group(_GroupName, Config) -> + [{client_type, erlang}, + {server_type, erlang} | Config]. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_testcase(raw_inet_option, Config) -> + ct:timetrap({seconds, 5}), + case os:type() of + {unix,linux} -> + Config; + _ -> + {skip, "Raw options are platform-specific"} + end; +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +getstat() -> + [{doc,"Test API function getstat/2"}]. + +getstat(Config) when is_list(Config) -> + ClientOpts = ?config(client_rsa_opts, Config), + ServerOpts = ?config(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port1 = ssl_test_lib:inet_port(Server1), + Server2 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port2 = ssl_test_lib:inet_port(Server2), + {ok, ActiveC} = rpc:call(ClientNode, ssl, connect, + [Hostname,Port1,[{active, once}|ClientOpts]]), + {ok, PassiveC} = rpc:call(ClientNode, ssl, connect, + [Hostname,Port2,[{active, false}|ClientOpts]]), + + ct:log("Testcase ~p, Client ~p Servers ~p, ~p ~n", + [self(), self(), Server1, Server2]), + + %% We only check that the values are non-zero initially + %% (due to the handshake), and that sending more changes the values. + + %% Passive socket. + + {ok, InitialStats} = ssl:getstat(PassiveC), + ct:pal("InitialStats ~p~n", [InitialStats]), + [true] = lists:usort([0 =/= proplists:get_value(Name, InitialStats) + || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), + + ok = ssl:send(PassiveC, "Hello world"), + wait_for_send(PassiveC), + {ok, SStats} = ssl:getstat(PassiveC, [send_cnt, send_oct]), + ct:pal("SStats ~p~n", [SStats]), + [true] = lists:usort([proplists:get_value(Name, SStats) =/= proplists:get_value(Name, InitialStats) + || Name <- [send_cnt, send_oct]]), + + %% Active socket. + + {ok, InitialAStats} = ssl:getstat(ActiveC), + ct:pal("InitialAStats ~p~n", [InitialAStats]), + [true] = lists:usort([0 =/= proplists:get_value(Name, InitialAStats) + || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), + + _ = receive + {ssl, ActiveC, _} -> + ok + after + ?SLEEP -> + exit(timeout) + end, + + ok = ssl:send(ActiveC, "Hello world"), + wait_for_send(ActiveC), + {ok, ASStats} = ssl:getstat(ActiveC, [send_cnt, send_oct]), + ct:pal("ASStats ~p~n", [ASStats]), + [true] = lists:usort([proplists:get_value(Name, ASStats) =/= proplists:get_value(Name, InitialAStats) + || Name <- [send_cnt, send_oct]]), + + ok. +%%-------------------------------------------------------------------- +socket_options() -> + [{doc,"Test API function getopts/2 and setopts/2"}]. + +socket_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Values = [{mode, list}, {active, true}], + %% Shall be the reverse order of Values! + Options = [active, mode], + + NewValues = [{mode, binary}, {active, once}], + %% Shall be the reverse order of NewValues! + NewOptions = [active, mode], + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + + {ok, Listen} = ssl:listen(0, ServerOpts), + {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), + ok = ssl:setopts(Listen, [{mode, binary}]), + {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), + {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), + ssl:close(Listen). + +%%-------------------------------------------------------------------- +raw_inet_option() -> + [{doc,"Ensure that a single 'raw' option is passed to ssl:listen correctly."}]. + +raw_inet_option(Config) when is_list(Config) -> + % 'raw' option values are platform-specific; these are the Linux values: + IpProtoTcp = 6, + % Use TCP_KEEPIDLE, because (e.g.) TCP_MAXSEG can't be read back reliably. + TcpKeepIdle = 4, + KeepAliveTimeSecs = 55, + LOptions = [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}], + {ok, LSocket} = ssl:listen(0, LOptions), + % Per http://www.erlang.org/doc/man/inet.html#getopts-2, we have to specify + % exactly which raw option we want, and the size of the buffer. + {ok, [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}]} = + ssl:getopts(LSocket, [{raw, IpProtoTcp, TcpKeepIdle, 4}]). + +%%-------------------------------------------------------------------- + +invalid_inet_get_option() -> + [{doc,"Test handling of invalid inet options in getopts"}]. + +invalid_inet_get_option(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_get_option_not_list() -> + [{doc,"Test handling of invalid type in getopts"}]. + +invalid_inet_get_option_not_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option_not_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_get_option_improper_list() -> + [{doc,"Test handling of invalid type in getopts"}]. + +invalid_inet_get_option_improper_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option_improper_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_set_option() -> + [{doc,"Test handling of invalid inet options in setopts"}]. + +invalid_inet_set_option(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_set_option_not_list() -> + [{doc,"Test handling of invalid type in setopts"}]. + +invalid_inet_set_option_not_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option_not_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_set_option_improper_list() -> + [{doc,"Test handling of invalid tye in setopts"}]. + +invalid_inet_set_option_improper_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option_improper_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> + %% Test get/set emulated opts + {ok, DefaultValues} = ssl:getopts(Socket, Options), + ssl:setopts(Socket, NewValues), + {ok, NewValues} = ssl:getopts(Socket, NewOptions), + %% Test get/set inet opts + {ok,[{reuseaddr, _}]} = ssl:getopts(Socket, [reuseaddr]), + {ok, All} = ssl:getopts(Socket, []), + ct:log("All opts ~p~n", [All]), + ok. + +get_invalid_inet_option(Socket) -> + {error, {options, {socket_options, foo, _}}} = ssl:getopts(Socket, [foo]), + ok. + +get_invalid_inet_option_not_list(Socket) -> + {error, {options, {socket_options, some_invalid_atom_here}}} + = ssl:getopts(Socket, some_invalid_atom_here), + ok. + +get_invalid_inet_option_improper_list(Socket) -> + {error, {options, {socket_options, foo,_}}} = ssl:getopts(Socket, [packet | foo]), + ok. + +set_invalid_inet_option(Socket) -> + {error, {options, {socket_options, {packet, foo}}}} = ssl:setopts(Socket, [{packet, foo}]), + {error, {options, {socket_options, {header, foo}}}} = ssl:setopts(Socket, [{header, foo}]), + {error, {options, {socket_options, {active, foo}}}} = ssl:setopts(Socket, [{active, foo}]), + {error, {options, {socket_options, {mode, foo}}}} = ssl:setopts(Socket, [{mode, foo}]), + ok. + +set_invalid_inet_option_not_list(Socket) -> + {error, {options, {not_a_proplist, some_invalid_atom_here}}} + = ssl:setopts(Socket, some_invalid_atom_here), + ok. + +set_invalid_inet_option_improper_list(Socket) -> + {error, {options, {not_a_proplist, [{packet, 0} | {foo, 2}]}}} = + ssl:setopts(Socket, [{packet, 0} | {foo, 2}]), + ok. + +wait_for_send(Socket) -> + %% Make sure TLS process processed send message event + _ = ssl:connection_information(Socket). + diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index c3e64e62d6..7dd27fb5cb 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -45,26 +45,35 @@ run_where(_, ipv6) -> Host = rpc:call(ServerNode, net_adm, localhost, []), {ClientNode, ServerNode, Host}. -node_to_hostip(Node) -> +node_to_hostip(Node, Role) -> [_ , Host] = string:tokens(atom_to_list(Node), "@"), {ok, Address} = inet:getaddr(Host, inet), + %% Convert client addresses in 127.0.0.0/24 subnet to the atom 'localhost'. + %% This is a workaround for testcase problems caused by the fact that + %% inet:peername/1 and inet:getaddr/2 return different addresses when + %% running on localhost. + normalize_loopback(Address, Role). + +normalize_loopback({127,_,_,_}, client) -> + localhost; +normalize_loopback(Address, _) -> Address. start_server(Args) -> - Result = spawn_link(?MODULE, run_server, [Args]), + Node = proplists:get_value(node, Args), + Result = spawn_link(Node, ?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), Transport = proplists:get_value(transport, Opts, ssl), ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]), - {ok, ListenSocket} = rpc:call(Node, Transport, listen, [Port, Options]), + {ok, ListenSocket} = Transport:listen(Port, Options), Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), run_server(ListenSocket, Opts). @@ -90,13 +99,12 @@ do_run_server(_, ok = Result, Opts) -> Pid = proplists:get_value(from, Opts), Pid ! {self(), Result}; do_run_server(ListenSocket, AcceptSocket, Opts) -> - Node = proplists:get_value(node, Opts), Pid = proplists:get_value(from, Opts), Transport = proplists:get_value(transport, Opts, ssl), {Module, Function, Args} = proplists:get_value(mfa, Opts), ct:log("~p:~p~nServer: apply(~p,~p,~p)~n", [?MODULE,?LINE, Module, Function, [AcceptSocket | Args]]), - case rpc:call(Node, Module, Function, [AcceptSocket | Args]) of + case apply(Module, Function, [AcceptSocket | Args]) of no_result_msg -> ok; Msg -> @@ -110,8 +118,8 @@ do_run_server(ListenSocket, AcceptSocket, Opts) -> run_server(ListenSocket, [MFA | proplists:delete(mfa, Opts)]); close -> ct:log("~p:~p~nServer closing ~p ~n", [?MODULE,?LINE, self()]), - Result = rpc:call(Node, Transport, close, [AcceptSocket], 500), - Result1 = rpc:call(Node, Transport, close, [ListenSocket], 500), + Result = Transport:close(AcceptSocket), + Result1 = Transport:close(ListenSocket), ct:log("~p:~p~nResult ~p : ~p ~n", [?MODULE,?LINE, Result, Result1]); {ssl_closed, _} -> ok @@ -132,41 +140,38 @@ connect(#sslsocket{} = ListenSocket, Opts) -> remove_close_msg(ReconnectTimes), AcceptSocket end; -connect(ListenSocket, Opts) -> - Node = proplists:get_value(node, Opts), +connect(ListenSocket, _Opts) -> ct:log("~p:~p~ngen_tcp:accept(~p)~n", [?MODULE,?LINE, ListenSocket]), - {ok, AcceptSocket} = rpc:call(Node, gen_tcp, accept, - [ListenSocket]), + {ok, AcceptSocket} = gen_tcp:accept(ListenSocket), AcceptSocket. connect(_, _, 0, AcceptSocket, _, _, _) -> AcceptSocket; connect(ListenSocket, Node, _N, _, Timeout, SslOpts, cancel) -> ct:log("ssl:transport_accept(~p)~n", [ListenSocket]), - {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, - [ListenSocket]), + {ok, AcceptSocket} = ssl:transport_accept(ListenSocket), ct:log("~p:~p~nssl:handshake(~p,~p,~p)~n", [?MODULE,?LINE, AcceptSocket, SslOpts,Timeout]), - case rpc:call(Node, ssl, handshake, [AcceptSocket, SslOpts, Timeout]) of + case ssl:handshake(AcceptSocket, SslOpts, Timeout) of {ok, Socket0, Ext} -> ct:log("Ext ~p:~n", [Ext]), ct:log("~p:~p~nssl:handshake_cancel(~p)~n", [?MODULE,?LINE, Socket0]), - rpc:call(Node, ssl, handshake_cancel, [Socket0]); + ssl:handshake_cancel(Socket0); Result -> ct:log("~p:~p~nssl:handshake@~p ret ~p",[?MODULE,?LINE, Node,Result]), Result end; connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) -> ct:log("ssl:transport_accept(~p)~n", [ListenSocket]), - {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, - [ListenSocket]), + {ok, AcceptSocket} = ssl:transport_accept(ListenSocket), ct:log("~p:~p~nssl:handshake(~p,~p,~p)~n", [?MODULE,?LINE, AcceptSocket, SslOpts,Timeout]), - case rpc:call(Node, ssl, handshake, [AcceptSocket, SslOpts, Timeout]) of + case ssl:handshake(AcceptSocket, SslOpts, Timeout) of {ok, Socket0, Ext} -> + [_|_] = maps:get(sni, Ext), ct:log("Ext ~p:~n", [Ext]), ct:log("~p:~p~nssl:handshake_continue(~p,~p,~p)~n", [?MODULE,?LINE, Socket0, ContOpts,Timeout]), - case rpc:call(Node, ssl, handshake_continue, [Socket0, ContOpts, Timeout]) of + case ssl:handshake_continue(Socket0, ContOpts, Timeout) of {ok, Socket} -> connect(ListenSocket, Node, N-1, Socket, Timeout, SslOpts, ContOpts); Error -> @@ -179,71 +184,144 @@ connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) -> end; connect(ListenSocket, Node, N, _, Timeout, [], ContOpts) -> ct:log("ssl:transport_accept(~p)~n", [ListenSocket]), - {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, - [ListenSocket]), + {ok, AcceptSocket} = ssl:transport_accept(ListenSocket), ct:log("~p:~p~nssl:ssl_accept(~p, ~p)~n", [?MODULE,?LINE, AcceptSocket, Timeout]), - case rpc:call(Node, ssl, ssl_accept, [AcceptSocket, Timeout]) of - ok -> - connect(ListenSocket, Node, N-1, AcceptSocket, Timeout, [], ContOpts); + case ssl:handshake(AcceptSocket, Timeout) of + {ok, Socket} -> + connect(ListenSocket, Node, N-1, Socket, Timeout, [], ContOpts); Result -> - ct:log("~p:~p~nssl:ssl_accept@~p ret ~p",[?MODULE,?LINE, Node,Result]), + ct:log("~p:~p~nssl:handshake@~p ret ~p",[?MODULE,?LINE, Node,Result]), Result end; -connect(ListenSocket, Node, _, _, Timeout, Opts, _) -> +connect(ListenSocket, _Node, _, _, Timeout, Opts, _) -> ct:log("ssl:transport_accept(~p)~n", [ListenSocket]), - {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, - [ListenSocket]), - ct:log("ssl:ssl_accept(~p,~p, ~p)~n", [AcceptSocket, Opts, Timeout]), - rpc:call(Node, ssl, ssl_accept, [AcceptSocket, Opts, Timeout]), + {ok, AcceptSocket} = ssl:transport_accept(ListenSocket), + ct:log("ssl:handshake(~p,~p, ~p)~n", [AcceptSocket, Opts, Timeout]), + ssl:handshake(AcceptSocket, Opts, Timeout), AcceptSocket. start_server_transport_abuse_socket(Args) -> - Result = spawn_link(?MODULE, transport_accept_abuse, [Args]), + Node = proplists:get_value(node, Args), + Result = spawn_link(Node, ?MODULE, transport_accept_abuse, [Args]), receive {listen, up} -> Result end. start_server_transport_control(Args) -> - Result = spawn_link(?MODULE, transport_switch_control, [Args]), + Node = proplists:get_value(node, Args), + Result = spawn_link(Node, ?MODULE, transport_switch_control, [Args]), receive {listen, up} -> Result end. +start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ClientOpts = ErlangClientOpts ++ ClientOpts0, + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = case OpensslServerOpts of + [] -> + ["s_server", "-accept", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile,"-key", KeyFile]; + [Opt, Value] -> + ["s_server", Opt, Value, "-accept", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile,"-key", KeyFile] + end, + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + active_recv, [length(Data)]}}, + {options, ClientOpts}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + transport_accept_abuse(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), Transport = proplists:get_value(transport, Opts, ssl), ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]), - {ok, ListenSocket} = rpc:call(Node, Transport, listen, [Port, Options]), + {ok, ListenSocket} = Transport:listen(Port, Options), Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), - {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, - [ListenSocket]), - {error, _} = rpc:call(Node, ssl, connection_information, [AcceptSocket]), - _ = rpc:call(Node, ssl, handshake, [AcceptSocket, infinity]), + {ok, AcceptSocket} = ssl:transport_accept(ListenSocket), + {error, _} = ssl:connection_information(AcceptSocket), + _ = ssl:handshake(AcceptSocket, infinity), Pid ! {self(), ok}. +start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenSSLClientOpts, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = ErlangServerOpts ++ ServerOpts0, + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, active_recv, [length(Data)]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client"] ++ OpenSSLClientOpts ++ ["-msg", "-connect", + hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version)], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). transport_switch_control(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), Transport = proplists:get_value(transport, Opts, ssl), ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]), - {ok, ListenSocket} = rpc:call(Node, Transport, listen, [Port, Options]), + {ok, ListenSocket} = Transport:listen(Port, Options), Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), - {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, - [ListenSocket]), - ok = rpc:call(Node, ssl, controlling_process, [AcceptSocket, self()]), + {ok, AcceptSocket} = ssl:transport_accept(ListenSocket), + ok = ssl:controlling_process(AcceptSocket, self()), Pid ! {self(), ok}. @@ -256,7 +334,8 @@ remove_close_msg(ReconnectTimes) -> end. start_client(Args) -> - Result = spawn_link(?MODULE, run_client_init, [lists:delete(return_socket, Args)]), + Node = proplists:get_value(node, Args), + Result = spawn_link(Node, ?MODULE, run_client_init, [lists:delete(return_socket, Args)]), receive {connected, Socket} -> case lists:member(return_socket, Args) of @@ -288,8 +367,8 @@ run_client(Opts) -> client_cont_loop(Node, Host, Port, Pid, Transport, Options, ContOpts, Opts) end. -client_loop(Node, Host, Port, Pid, Transport, Options, Opts) -> - case rpc:call(Node, Transport, connect, [Host, Port, Options]) of +client_loop(_Node, Host, Port, Pid, Transport, Options, Opts) -> + case Transport:connect(Host, Port, Options) of {ok, Socket} -> Pid ! {connected, Socket}, ct:log("~p:~p~nClient: connected~n", [?MODULE,?LINE]), @@ -299,7 +378,7 @@ client_loop(Node, Host, Port, Pid, Transport, Options, Opts) -> {Module, Function, Args} = proplists:get_value(mfa, Opts), ct:log("~p:~p~nClient: apply(~p,~p,~p)~n", [?MODULE,?LINE, Module, Function, [Socket | Args]]), - case rpc:call(Node, Module, Function, [Socket | Args]) of + case apply(Module, Function, [Socket | Args]) of no_result_msg -> ok; Msg -> @@ -309,7 +388,7 @@ client_loop(Node, Host, Port, Pid, Transport, Options, Opts) -> receive close -> ct:log("~p:~p~nClient closing~n", [?MODULE,?LINE]), - rpc:call(Node, Transport, close, [Socket]); + Transport:close(Socket); {ssl_closed, Socket} -> ok; {gen_tcp, closed} -> @@ -339,16 +418,13 @@ client_loop(Node, Host, Port, Pid, Transport, Options, Opts) -> end; {error, Reason} -> ct:log("~p:~p~nClient: connection failed: ~p ~n", [?MODULE,?LINE, Reason]), - Pid ! {connect_failed, Reason}; - {badrpc,BadRPC} -> - ct:log("~p:~p~nBad rpc: ~p",[?MODULE,?LINE, BadRPC]), - Pid ! {connect_failed, {badrpc,BadRPC}} + Pid ! {connect_failed, Reason} end. -client_cont_loop(Node, Host, Port, Pid, Transport, Options, cancel, _Opts) -> - case rpc:call(Node, Transport, connect, [Host, Port, Options]) of +client_cont_loop(_Node, Host, Port, Pid, Transport, Options, cancel, _Opts) -> + case Transport:connect(Host, Port, Options) of {ok, Socket, _} -> - Result = rpc:call(Node, Transport, handshake_cancel, [Socket]), + Result = Transport:handshake_cancel(Socket), ct:log("~p:~p~nClient: Cancel: ~p ~n", [?MODULE,?LINE, Result]), Pid ! {connect_failed, Result}; {error, Reason} -> @@ -356,17 +432,17 @@ client_cont_loop(Node, Host, Port, Pid, Transport, Options, cancel, _Opts) -> Pid ! {connect_failed, Reason} end; -client_cont_loop(Node, Host, Port, Pid, Transport, Options, ContOpts, Opts) -> - case rpc:call(Node, Transport, connect, [Host, Port, Options]) of +client_cont_loop(_Node, Host, Port, Pid, Transport, Options, ContOpts, Opts) -> + case Transport:connect(Host, Port, Options) of {ok, Socket0, _} -> ct:log("~p:~p~nClient: handshake_continue(~p, ~p, infinity) ~n", [?MODULE, ?LINE, Socket0, ContOpts]), - case rpc:call(Node, Transport, handshake_continue, [Socket0, ContOpts]) of + case Transport:handshake_continue(Socket0, ContOpts) of {ok, Socket} -> Pid ! {connected, Socket}, {Module, Function, Args} = proplists:get_value(mfa, Opts), ct:log("~p:~p~nClient: apply(~p,~p,~p)~n", [?MODULE,?LINE, Module, Function, [Socket | Args]]), - case rpc:call(Node, Module, Function, [Socket | Args]) of + case apply(Module, Function, [Socket | Args]) of no_result_msg -> ok; Msg -> @@ -403,14 +479,16 @@ close(Pid, Timeout) -> exit(Pid, kill) end. -check_result(Server, ServerMsg, Client, ClientMsg) -> +check_result(Server, ServerMsg, Client, ClientMsg) -> + {ClientIP, ClientPort} = get_ip_port(ServerMsg), receive {Server, ServerMsg} -> check_result(Client, ClientMsg); - + %% Workaround to accept local addresses (127.0.0.0/24) + {Server, {ok, {{127,_,_,_}, ClientPort}}} when ClientIP =:= localhost -> + check_result(Client, ClientMsg); {Client, ClientMsg} -> check_result(Server, ServerMsg); - {Port, {data,Debug}} when is_port(Port) -> ct:log("~p:~p~n Openssl ~s~n",[?MODULE,?LINE, Debug]), check_result(Server, ServerMsg, Client, ClientMsg); @@ -423,10 +501,14 @@ check_result(Server, ServerMsg, Client, ClientMsg) -> ct:fail(Reason) end. -check_result(Pid, Msg) -> +check_result(Pid, Msg) -> + {ClientIP, ClientPort} = get_ip_port(Msg), receive {Pid, Msg} -> ok; + %% Workaround to accept local addresses (127.0.0.0/24) + {Pid, {ok, {{127,_,_,_}, ClientPort}}} when ClientIP =:= localhost -> + ok; {Port, {data,Debug}} when is_port(Port) -> ct:log("~p:~p~n Openssl ~s~n",[?MODULE,?LINE, Debug]), check_result(Pid,Msg); @@ -438,37 +520,63 @@ check_result(Pid, Msg) -> {got, Unexpected}}, ct:fail(Reason) end. + + +get_ip_port({ok,{ClientIP, ClientPort}}) -> + {ClientIP, ClientPort}; +get_ip_port(_) -> + {undefined, undefined}. + + check_server_alert(Pid, Alert) -> receive - {Pid, {error, {tls_alert, {Alert, _}}}} -> + {Pid, {error, {tls_alert, {Alert, STxt}}}} -> + check_server_txt(STxt), + ok; + {Pid, {error, closed}} -> ok end. check_server_alert(Server, Client, Alert) -> receive - {Server, {error, {tls_alert, {Alert, _}}}} -> - receive - {Client, {error, {tls_alert, {Alert, _}}}} -> - ok; - {Client, {error, closed}} -> - ok - end + {Server, {error, {tls_alert, {Alert, STxt}}}} -> + check_server_txt(STxt), + check_client_alert(Client, Alert) end. check_client_alert(Pid, Alert) -> receive - {Pid, {error, {tls_alert, {Alert, _}}}} -> + {Pid, {error, {tls_alert, {Alert, CTxt}}}} -> + check_client_txt(CTxt), + ok; + {Pid, {ssl_error, _, {tls_alert, {Alert, CTxt}}}} -> + check_client_txt(CTxt), + ok; + {Pid, {error, closed}} -> ok end. check_client_alert(Server, Client, Alert) -> receive - {Client, {error, {tls_alert, {Alert, _}}}} -> - receive - {Server, {error, {tls_alert, {Alert, _}}}} -> - ok; - {Server, {error, closed}} -> - ok - end + {Client, {error, {tls_alert, {Alert, CTxt}}}} -> + check_client_txt(CTxt), + check_server_alert(Server, Alert); + {Client, {ssl_error, _, {tls_alert, {Alert, CTxt}}}} -> + check_client_txt(CTxt), + ok; + {Client, {error, closed}} -> + ok end. +check_server_txt("TLS server" ++ _) -> + ok; +check_server_txt("DTLS server" ++ _) -> + ok; +check_server_txt(Txt) -> + ct:fail({expected_server, {got, Txt}}). +check_client_txt("TLS client" ++ _) -> + ok; +check_client_txt("DTLS client" ++ _) -> + ok; +check_client_txt(Txt) -> + ct:fail({expected_server, {got, Txt}}). wait_for_result(Server, ServerMsg, Client, ClientMsg) -> receive @@ -514,13 +622,6 @@ wait_for_result(Pid, Msg) -> %% Unexpected end. -user_lookup(psk, _Identity, UserState) -> - {ok, UserState}; -user_lookup(srp, Username, _UserState) -> - Salt = ssl_cipher:random_bytes(16), - UserPassHash = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, <<"secret">>])]), - {ok, {srp_1024, Salt, UserPassHash}}. - cert_options(Config) -> ClientCaCertFile = filename:join([proplists:get_value(priv_dir, Config), "client", "cacerts.pem"]), @@ -547,8 +648,7 @@ cert_options(Config) -> "badcert.pem"]), BadKeyFile = filename:join([proplists:get_value(priv_dir, Config), "badkey.pem"]), - PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, - + [{client_opts, [{cacertfile, ClientCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}]}, @@ -562,30 +662,6 @@ cert_options(Config) -> {ssl_imp, new}]}, {server_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, - {client_psk, [{ssl_imp, new}, - {psk_identity, "Test-User"}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk, [{ssl_imp, new},{reuseaddr, true}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk_hint, [{ssl_imp, new},{reuseaddr, true}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {psk_identity, "HINT"}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk_anon, [{ssl_imp, new},{reuseaddr, true}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk_anon_hint, [{ssl_imp, new},{reuseaddr, true}, - {psk_identity, "HINT"}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {client_srp, [{ssl_imp, new}, - {srp_identity, {"Test-User", "secret"}}]}, - {server_srp, [{ssl_imp, new},{reuseaddr, true}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {user_lookup_fun, {fun user_lookup/3, undefined}}, - {ciphers, srp_suites()}]}, - {server_srp_anon, [{ssl_imp, new},{reuseaddr, true}, - {user_lookup_fun, {fun user_lookup/3, undefined}}, - {ciphers, srp_anon_suites()}]}, {server_verification_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ClientCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, @@ -626,15 +702,32 @@ make_dsa_cert(Config) -> [{server_dsa_opts, ServerConf}, {server_dsa_verify_opts, [{verify, verify_peer} | ServerConf]}, - {client_dsa_opts, ClientConf}, - {server_srp_dsa, [{user_lookup_fun, {fun user_lookup/3, undefined}}, - {ciphers, srp_dss_suites()} | ServerConf]}, - {client_srp_dsa, [{srp_identity, {"Test-User", "secret"}} - | ClientConf]} + {client_dsa_opts, ClientConf} | Config]; false -> Config end. + + +make_cert_chains_der(Alg, UserConf) -> + ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()), + ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()), + CertChainConf = gen_conf(Alg, Alg, ClientChain, ServerChain), + public_key:pkix_test_data(CertChainConf). + +make_cert_chains_pem(Alg, UserConf, Config, Suffix) -> + ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()), + ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()), + CertChainConf = gen_conf(Alg, Alg, ClientChain, ServerChain), + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]), + GenCertData = public_key:pkix_test_data(CertChainConf), + Conf = x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + CConf = proplists:get_value(client_config, Conf), + SConf = proplists:get_value(server_config, Conf), + #{server_config => SConf, + client_config => CConf}. + make_rsa_cert_chains(UserConf, Config, Suffix) -> ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()), ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()), @@ -649,6 +742,40 @@ make_rsa_cert_chains(UserConf, Config, Suffix) -> [{reuseaddr, true}, {verify, verify_peer} | ServerConf] }. +make_ecc_cert_chains(UserConf, Config, Suffix) -> + ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()), + ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()), + CertChainConf = gen_conf(ecdsa, ecdsa, ClientChain, ServerChain), + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdsa" ++ Suffix]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "ecdsa" ++ Suffix]), + GenCertData = public_key:pkix_test_data(CertChainConf), + [{server_config, ServerConf}, + {client_config, ClientConf}] = + x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + {[{verify, verify_peer} | ClientConf], + [{reuseaddr, true}, {verify, verify_peer} | ServerConf] + }. + + +make_dsa_cert_chains(UserConf, Config, Suffix) -> + CryptoSupport = crypto:supports(), + case proplists:get_bool(dss, proplists:get_value(public_keys, CryptoSupport)) of + true -> + ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()), + ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()), + CertChainConf = gen_conf(dsa, dsa, ClientChain, ServerChain), + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "dsa" ++ Suffix]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "dsa" ++ Suffix]), + GenCertData = public_key:pkix_test_data(CertChainConf), + [{server_config, ServerConf}, + {client_config, ClientConf}] = + x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + {[{verify, verify_peer} | ClientConf], + [{reuseaddr, true}, {verify, verify_peer} | ServerConf]}; + false -> + Config + end. + make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config) -> make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config, ?DEFAULT_CURVE). %% @@ -903,14 +1030,14 @@ make_ecdh_rsa_cert(Config) -> end. start_upgrade_server(Args) -> - Result = spawn_link(?MODULE, run_upgrade_server, [Args]), + Node = proplists:get_value(node, Args), + Result = spawn_link(Node, ?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), @@ -918,43 +1045,41 @@ run_upgrade_server(Opts) -> Pid = proplists:get_value(from, Opts), ct:log("~p:~p~ngen_tcp:listen(~p, ~p)~n", [?MODULE,?LINE, Port, TcpOptions]), - {ok, ListenSocket} = rpc:call(Node, gen_tcp, listen, [Port, TcpOptions]), + {ok, ListenSocket} = gen_tcp:listen(Port, TcpOptions), Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), ct:log("~p:~p~ngen_tcp:accept(~p)~n", [?MODULE,?LINE, ListenSocket]), - {ok, AcceptSocket} = rpc:call(Node, gen_tcp, accept, [ListenSocket]), + {ok, AcceptSocket} = gen_tcp:accept(ListenSocket), try {ok, SslAcceptSocket} = case TimeOut of infinity -> - ct:log("~p:~p~nssl:ssl_accept(~p, ~p)~n", + ct:log("~p:~p~nssl:handshake(~p, ~p)~n", [?MODULE,?LINE, AcceptSocket, SslOptions]), - rpc:call(Node, ssl, ssl_accept, - [AcceptSocket, SslOptions]); + ssl:handshake(AcceptSocket, SslOptions); _ -> - ct:log("~p:~p~nssl:ssl_accept(~p, ~p, ~p)~n", + ct:log("~p:~p~nssl:handshake(~p, ~p, ~p)~n", [?MODULE,?LINE, AcceptSocket, SslOptions, TimeOut]), - rpc:call(Node, ssl, ssl_accept, - [AcceptSocket, SslOptions, TimeOut]) + ssl:handshake(AcceptSocket, SslOptions, TimeOut) end, {Module, Function, Args} = proplists:get_value(mfa, Opts), - Msg = rpc:call(Node, Module, Function, [SslAcceptSocket | Args]), + Msg = apply(Module, Function, [SslAcceptSocket | Args]), ct:log("~p:~p~nUpgrade Server Msg: ~p ~n", [?MODULE,?LINE, Msg]), Pid ! {self(), Msg}, receive close -> ct:log("~p:~p~nUpgrade Server closing~n", [?MODULE,?LINE]), - rpc:call(Node, ssl, close, [SslAcceptSocket]) + ssl:close(SslAcceptSocket) end catch error:{badmatch, Error} -> Pid ! {self(), Error} end. start_upgrade_client(Args) -> - spawn_link(?MODULE, run_upgrade_client, [Args]). + Node = proplists:get_value(node, Args), + spawn_link(Node, ?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), @@ -963,34 +1088,34 @@ run_upgrade_client(Opts) -> ct:log("~p:~p~ngen_tcp:connect(~p, ~p, ~p)~n", [?MODULE,?LINE, Host, Port, TcpOptions]), - {ok, Socket} = rpc:call(Node, gen_tcp, connect, [Host, Port, TcpOptions]), + {ok, Socket} = gen_tcp:connect(Host, Port, TcpOptions), send_selected_port(Pid, Port, Socket), ct:log("~p:~p~nssl:connect(~p, ~p)~n", [?MODULE,?LINE, Socket, SslOptions]), - {ok, SslSocket} = rpc:call(Node, ssl, connect, [Socket, SslOptions]), + {ok, SslSocket} = ssl:connect(Socket, SslOptions), {Module, Function, Args} = proplists:get_value(mfa, Opts), ct:log("~p:~p~napply(~p, ~p, ~p)~n", [?MODULE,?LINE, Module, Function, [SslSocket | Args]]), - Msg = rpc:call(Node, Module, Function, [SslSocket | Args]), + Msg = apply(Module, Function, [SslSocket | Args]), ct:log("~p:~p~nUpgrade Client Msg: ~p ~n", [?MODULE,?LINE, Msg]), Pid ! {self(), Msg}, receive close -> ct:log("~p:~p~nUpgrade Client closing~n", [?MODULE,?LINE]), - rpc:call(Node, ssl, close, [SslSocket]) + ssl:close(SslSocket) end. start_upgrade_server_error(Args) -> - Result = spawn_link(?MODULE, run_upgrade_server_error, [Args]), + Node = proplists:get_value(node, Args), + Result = spawn_link(Node,?MODULE, run_upgrade_server_error, [Args]), receive {listen, up} -> Result end. run_upgrade_server_error(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), @@ -998,22 +1123,20 @@ run_upgrade_server_error(Opts) -> Pid = proplists:get_value(from, Opts), ct:log("~p:~p~ngen_tcp:listen(~p, ~p)~n", [?MODULE,?LINE, Port, TcpOptions]), - {ok, ListenSocket} = rpc:call(Node, gen_tcp, listen, [Port, TcpOptions]), + {ok, ListenSocket} = gen_tcp:listen(Port, TcpOptions), Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), ct:log("~p:~p~ngen_tcp:accept(~p)~n", [?MODULE,?LINE, ListenSocket]), - {ok, AcceptSocket} = rpc:call(Node, gen_tcp, accept, [ListenSocket]), + {ok, AcceptSocket} = gen_tcp:accept(ListenSocket), Error = case TimeOut of infinity -> - ct:log("~p:~p~nssl:ssl_accept(~p, ~p)~n", + ct:log("~p:~p~nssl:handshake(~p, ~p)~n", [?MODULE,?LINE, AcceptSocket, SslOptions]), - rpc:call(Node, ssl, ssl_accept, - [AcceptSocket, SslOptions]); + ssl:handshake(AcceptSocket, SslOptions); _ -> - ct:log("~p:~p~nssl:ssl_accept(~p, ~p, ~p)~n", + ct:log("~p:~p~nssl:ssl_handshake(~p, ~p, ~p)~n", [?MODULE,?LINE, AcceptSocket, SslOptions, TimeOut]), - rpc:call(Node, ssl, ssl_accept, - [AcceptSocket, SslOptions, TimeOut]) + ssl:handshake(AcceptSocket, SslOptions, TimeOut) end, Pid ! {self(), Error}. @@ -1025,32 +1148,31 @@ start_server_error(Args) -> 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), Transport = proplists:get_value(transport, Opts, ssl), ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]), - case rpc:call(Node, Transport, listen, [Port, Options]) of + case Transport:listen(Port, Options) of {ok, #sslsocket{} = ListenSocket} -> %% To make sure error_client will %% get {error, closed} and not {error, connection_refused} Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), ct:log("~p:~p~nssl:transport_accept(~p)~n", [?MODULE,?LINE, ListenSocket]), - case rpc:call(Node, Transport, transport_accept, [ListenSocket]) of + case Transport:transport_accept(ListenSocket) of {error, _} = Error -> Pid ! {self(), Error}; {ok, AcceptSocket} -> ct:log("~p:~p~nssl:ssl_accept(~p)~n", [?MODULE,?LINE, AcceptSocket]), - Error = rpc:call(Node, ssl, ssl_accept, [AcceptSocket]), + Error = ssl:handshake(AcceptSocket), Pid ! {self(), Error} end; {ok, ListenSocket} -> Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), ct:log("~p:~p~n~p:accept(~p)~n", [?MODULE,?LINE, Transport, ListenSocket]), - case rpc:call(Node, Transport, accept, [ListenSocket]) of + case Transport:accept(ListenSocket) of {error, _} = Error -> Pid ! {self(), Error} end; @@ -1062,18 +1184,26 @@ run_server_error(Opts) -> end. start_client_error(Args) -> - spawn_link(?MODULE, run_client_error, [Args]). + Node = proplists:get_value(node, Args), + spawn_link(Node, ?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), Transport = proplists:get_value(transport, Opts, ssl), Options = proplists:get_value(options, Opts), ct:log("~p:~p~nssl:connect(~p, ~p, ~p)~n", [?MODULE,?LINE, Host, Port, Options]), - Error = rpc:call(Node, Transport, connect, [Host, Port, Options]), - Pid ! {self(), Error}. + Error = Transport:connect(Host, Port, Options), + case Error of + {error, _} -> + Pid ! {self(), Error}; + {ok, _Socket} -> + receive + {ssl_error, _, {tls_alert, _}} = SslError -> + Pid ! {self(), SslError} + end + end. accepters(N) -> accepters([], N). @@ -1090,11 +1220,57 @@ accepters(Acc, N) -> basic_test(COpts, SOpts, Config) -> SType = proplists:get_value(server_type, Config), CType = proplists:get_value(client_type, Config), - {Server, Port} = start_server(SType, SOpts, Config), + {Server, Port} = start_server(SType, COpts, SOpts, Config), Client = start_client(CType, Port, COpts, Config), gen_check_result(Server, SType, Client, CType), stop(Server, Client). +basic_alert(ClientOpts, ServerOpts, Config, Alert) -> + SType = proplists:get_value(server_type, Config), + CType = proplists:get_value(client_type, Config), + run_basic_alert(SType, CType, ClientOpts, ServerOpts, Config, Alert). + +run_basic_alert(erlang, erlang, ClientOpts, ServerOpts, Config, Alert) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + + Port = inet_port(Server), + + Client = start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + check_server_alert(Server, Client, Alert); +run_basic_alert(openssl = SType, erlang, ClientOpts, ServerOpts, Config, Alert) -> + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + {_Server, Port} = start_server(SType, ClientOpts, ServerOpts, Config), + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + Client = start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + check_client_alert(Client, Alert); +run_basic_alert(erlang, openssl = CType, ClientOpts, ServerOpts, Config, Alert) -> + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = start_server_error([{node, ServerNode}, {port, 0}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = inet_port(Server), + start_client(CType, Port, ClientOpts, Config), + + check_server_alert(Server, Alert). + + ecc_test(Expect, COpts, SOpts, CECCOpts, SECCOpts, Config) -> {Server, Port} = start_server_ecc(erlang, SOpts, Expect, SECCOpts, Config), Client = start_client_ecc(erlang, Port, COpts, Expect, CECCOpts, Config), @@ -1106,16 +1282,52 @@ ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) -> Client = start_client_ecc_error(erlang, Port, COpts, CECCOpts, Config), check_server_alert(Server, Client, insufficient_security). -start_client(openssl, Port, ClientOpts, Config) -> +start_basic_client(openssl, Version, Port, ClientOpts) -> Cert = proplists:get_value(certfile, ClientOpts), Key = proplists:get_value(keyfile, ClientOpts), CA = proplists:get_value(cacertfile, ClientOpts), - Version = ssl_test_lib:protocol_version(Config), + Groups0 = proplists:get_value(groups, ClientOpts), Exe = "openssl", Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", Cert, "-CAfile", CA, - "-key", Key, "-host","localhost", "-msg", "-debug"], + "-CAfile", CA, "-host", "localhost", "-msg", "-debug"], + Args1 = + case Groups0 of + undefined -> + Args0; + G -> + Args0 ++ ["-groups", G] + end, + Args = + case {Cert, Key} of + {C, K} when C =:= undefined orelse + K =:= undefined -> + Args1; + {C, K} -> + Args1 ++ ["-cert", C, "-key", K] + end, + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + true = port_command(OpenSslPort, "Hello world"), + OpenSslPort. + +start_client(openssl, Port, ClientOpts, Config) -> + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)), + Groups0 = proplists:get_value(groups, ClientOpts), + CertArgs = openssl_cert_options(ClientOpts, client), + Exe = "openssl", + Args0 = case Groups0 of + undefined -> + ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version), + ciphers(Ciphers, Version), + ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]; + Group -> + ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version), + ciphers(Ciphers, Version), "-groups", Group, + ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"] + end, Args = maybe_force_ipv4(Args0), OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), @@ -1128,7 +1340,7 @@ start_client(erlang, Port, ClientOpts, Config) -> {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}}, - {options, [{verify, verify_peer} | ClientOpts]}]). + {options, ClientOpts}]). %% Workaround for running tests on machines where openssl %% s_client would use an IPv6 address with localhost. As @@ -1163,29 +1375,58 @@ start_client_ecc_error(erlang, Port, ClientOpts, ECCOpts, Config) -> [{verify, verify_peer} | ClientOpts]}]). -start_server(openssl, ServerOpts, Config) -> - Cert = proplists:get_value(certfile, ServerOpts), - Key = proplists:get_value(keyfile, ServerOpts), - CA = proplists:get_value(cacertfile, ServerOpts), +start_server(openssl, ClientOpts, ServerOpts, Config) -> Port = inet_port(node()), Version = protocol_version(Config), Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-verify", "2", "-cert", Cert, "-CAfile", CA, - "-key", Key, "-msg", "-debug"], + CertArgs = openssl_cert_options(ServerOpts, server), + Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)), + Groups0 = proplists:get_value(groups, ServerOpts), + Args = case Groups0 of + undefined -> + ["s_server", "-accept", integer_to_list(Port), cipher_flag(Version), + ciphers(Ciphers, Version), + ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]; + Group -> + ["s_server", "-accept", integer_to_list(Port), cipher_flag(Version), + ciphers(Ciphers, Version), "-groups", Group, + ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"] + end, OpenSslPort = portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), {OpenSslPort, Port}; -start_server(erlang, ServerOpts, Config) -> +start_server(erlang, _, ServerOpts, Config) -> {_, ServerNode, _} = ssl_test_lib:run_where(Config), KeyEx = proplists:get_value(check_keyex, Config, false), + Versions = protocol_versions(Config), Server = start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}}, - {options, [{verify, verify_peer} | ServerOpts]}]), + {options, [{verify, verify_peer}, {versions, Versions} | ServerOpts]}]), {Server, inet_port(Server)}. + +cipher_flag('tlsv1.3') -> + "-ciphersuites"; +cipher_flag(_) -> + "-cipher". + +ciphers(Ciphers, Version) -> + Strs = [ssl_cipher_format:suite_map_to_openssl_str(Cipher) || Cipher <- Ciphers], + ciphers_concat(Version, Strs, ""). + +ciphers_concat(_, [], [":" | Acc]) -> + lists:append(lists:reverse(Acc)); +ciphers_concat('tlsv1.3' = Version, [Head| Tail], Acc) -> + case Head of + "TLS" ++ _ -> + ciphers_concat(Version, Tail, [":", Head | Acc]); + _ -> + ciphers_concat(Version, Tail, Acc) + end; +ciphers_concat(Version, [Head| Tail], Acc) -> + ciphers_concat(Version, Tail, [":", Head | Acc]). start_server_with_raw_key(erlang, ServerOpts, Config) -> {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -1239,6 +1480,37 @@ stop(Client, Server) -> close(Server), close(Client). + +openssl_cert_options(Opts, Role) -> + Cert = proplists:get_value(certfile, Opts, undefined), + Key = proplists:get_value(keyfile, Opts, undefined), + CA = proplists:get_value(cacertfile, Opts, undefined), + case CA of + undefined -> + case cert_option("-cert", Cert) ++ cert_option("-key", Key) of + [] when Role == server -> + ["-nocert"]; + Other -> + Other + end; + _ -> + cert_option("-cert", Cert) ++ cert_option("-CAfile", CA) ++ + cert_option("-key", Key) ++ openssl_verify(Opts) ++ ["2"] + end. + +openssl_verify(Opts) -> + case proplists:get_value(fail_if_no_peer_cert, Opts, undefined) of + true -> + ["-Verify"]; + _ -> + ["-verify"] + end. + +cert_option(_, undefined) -> + []; +cert_option(Opt, Value) -> + [Opt, Value]. + supported_eccs(Opts) -> ToCheck = proplists:get_value(eccs, Opts, []), Supported = ssl:eccs(), @@ -1335,13 +1607,13 @@ common_ciphers(crypto) -> common_ciphers(openssl) -> OpenSslSuites = string:tokens(string:strip(os:cmd("openssl ciphers"), right, $\n), ":"), - [ssl_cipher_format:suite_definition(S) + [ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:suites(tls_record:highest_protocol_version([])), - lists:member(ssl_cipher_format:openssl_suite_name(S), OpenSslSuites) + lists:member(ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(S)), OpenSslSuites) ]. available_suites(Version) -> - [ssl_cipher_format:suite_definition(Suite) || + [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- ssl_cipher:filter_suites(ssl_cipher:suites(Version))]. @@ -1414,7 +1686,7 @@ string_regex_filter(_Str, _Search) -> false. ecdh_dh_anonymous_suites(Version) -> - ssl:filter_cipher_suites([ssl_cipher_format:suite_definition(S) || S <- ssl_cipher:anonymous_suites(Version)], + ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:anonymous_suites(Version)], [{key_exchange, fun(dh_anon) -> true; @@ -1424,7 +1696,7 @@ ecdh_dh_anonymous_suites(Version) -> false end}]). psk_suites({3,_} = Version) -> - ssl:filter_cipher_suites([ssl_cipher_format:suite_definition(S) || S <- ssl_cipher:psk_suites(Version)], []); + ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:psk_suites(Version)], []); psk_suites(Version) -> ssl:filter_cipher_suites(psk_suites(dtls_v1:corresponding_tls_version(Version)), [{cipher, @@ -1435,7 +1707,7 @@ psk_suites(Version) -> end}]). psk_anon_suites({3,_} = Version) -> - ssl:filter_cipher_suites([ssl_cipher_format:suite_definition(S) || S <- ssl_cipher:psk_suites_anon(Version)], + ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:psk_suites_anon(Version)], [{key_exchange, fun(psk) -> true; @@ -1458,7 +1730,7 @@ psk_anon_suites(Version) -> srp_suites() -> - ssl:filter_cipher_suites([ssl_cipher_format:suite_definition(S) || S <- ssl_cipher:srp_suites()], + ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:srp_suites()], [{key_exchange, fun(srp_rsa) -> true; @@ -1466,10 +1738,10 @@ srp_suites() -> false end}]). srp_anon_suites() -> - ssl:filter_cipher_suites([ssl_cipher_format:suite_definition(S) || S <- ssl_cipher:srp_suites_anon()], + ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:srp_suites_anon()], []). srp_dss_suites() -> - ssl:filter_cipher_suites([ssl_cipher_format:suite_definition(S) || S <- ssl_cipher:srp_suites()], + ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:srp_suites()], [{key_exchange, fun(srp_dss) -> true; @@ -1477,14 +1749,14 @@ srp_dss_suites() -> false end}]). chacha_suites(Version) -> - [ssl_cipher_format:suite_definition(S) || S <- ssl_cipher:filter_suites(ssl_cipher:chacha_suites(Version))]. + [ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:filter_suites(ssl_cipher:chacha_suites(Version))]. rc4_suites(Version) -> - ssl:filter_cipher_suites([ssl_cipher_format:suite_definition(S) || S <-ssl_cipher:rc4_suites(Version)], []). + ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <-ssl_cipher:rc4_suites(Version)], []). des_suites(Version) -> - ssl:filter_cipher_suites([ssl_cipher_format:suite_definition(S) || S <-ssl_cipher:des_suites(Version)], []). + ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <-ssl_cipher:des_suites(Version)], []). tuple_to_map({Kex, Cipher, Mac}) -> #{key_exchange => Kex, @@ -1511,10 +1783,13 @@ cipher_result(Socket, Result) -> ct:log("~p:~p~nSuccessfull connect: ~p~n", [?MODULE,?LINE, Result]), %% Importante to send two packets here %% to properly test "cipher state" handling - ssl:send(Socket, "Hello\n"), - "Hello\n" = active_recv(Socket, length( "Hello\n")), - ssl:send(Socket, " world\n"), - " world\n" = active_recv(Socket, length(" world\n")), + Hello = "Hello\n", + World = " world\n", + ssl:send(Socket, Hello), + ct:sleep(500), + ssl:send(Socket, World), + Expected = Hello ++ World, + Expected = active_recv(Socket, length(Expected)), ok. session_info_result(Socket) -> @@ -1558,6 +1833,8 @@ is_tls_version('dtlsv1.2') -> true; is_tls_version('dtlsv1') -> true; +is_tls_version('tlsv1.3') -> + true; is_tls_version('tlsv1.2') -> true; is_tls_version('tlsv1.1') -> @@ -1588,7 +1865,12 @@ init_tls_version(Version, Config) -> clean_tls_version(Config) -> proplists:delete(protocol_opts, proplists:delete(protocol, Config)). - + +sufficient_crypto_support(Version) + when Version == 'tlsv1.3' -> + CryptoSupport = crypto:supports(), + lists:member(rsa_pkcs1_pss_padding, proplists:get_value(rsa_opts, CryptoSupport)) andalso + lists:member(x448, proplists:get_value(curves, CryptoSupport)); sufficient_crypto_support(Version) when Version == 'tlsv1.2'; Version == 'dtlsv1.2' -> CryptoSupport = crypto:supports(), @@ -1742,6 +2024,15 @@ is_sane_ecc(crypto) -> is_sane_ecc(_) -> sufficient_crypto_support(cipher_ec). +is_sane_oppenssl_client() -> + [{_,_, Bin}] = crypto:info_lib(), + case binary_to_list(Bin) of + "OpenSSL 0.9" ++ _ -> + false; + _ -> + true + end. + is_fips(openssl) -> VersionStr = os:cmd("openssl version"), case re:split(VersionStr, "fips") of @@ -1874,12 +2165,61 @@ check_sane_openssl_version(Version) -> false; {'tlsv1.1', "OpenSSL 0" ++ _} -> false; + {'tlsv1', "OpenSSL 0" ++ _} -> + false; {_, _} -> true end; false -> false end. +check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1.1'; + Version == 'tlsv1.2' -> + case os:cmd("openssl version") of + "OpenSSL 1.0.1c" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 1.0.1b" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 1.0.1a" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 1.0.1 " ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + _ -> + check_sane_openssl_renegotaite(Config) + end; +check_sane_openssl_renegotaite(Config, _) -> + check_sane_openssl_renegotaite(Config). + +check_sane_openssl_renegotaite(Config) -> + case os:cmd("openssl version") of + "OpenSSL 1.0.0" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 0.9.8" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 0.9.7" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + _ -> + Config + end. + +workaround_openssl_s_clinent() -> + %% http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=683159 + %% https://bugs.archlinux.org/task/33919 + %% Bug seems to manifests it self if TLS version is not + %% explicitly specified + case os:cmd("openssl version") of + "OpenSSL 1.0.1c" ++ _ -> + ["-no_tls1_2"]; + "OpenSSL 1.0.1d" ++ _ -> + ["-no_tls1_2"]; + "OpenSSL 1.0.1e" ++ _ -> + ["-no_tls1_2"]; + "OpenSSL 1.0.1f" ++ _ -> + ["-no_tls1_2"]; + _ -> + [] + end. + enough_openssl_crl_support("OpenSSL 0." ++ _) -> false; enough_openssl_crl_support(_) -> true. @@ -1906,6 +2246,8 @@ version_flag('tlsv1.1') -> "-tls1_1"; version_flag('tlsv1.2') -> "-tls1_2"; +version_flag('tlsv1.3') -> + "-tls1_3"; version_flag(sslv3) -> "-ssl3"; version_flag(sslv2) -> @@ -1916,10 +2258,10 @@ version_flag('dtlsv1') -> "-dtls1". filter_suites([Cipher | _] = Ciphers, AtomVersion) when is_list(Cipher)-> - filter_suites([ssl_cipher_format:openssl_suite(S) || S <- Ciphers], + filter_suites([ssl_cipher_format:suite_openssl_str_to_map(S) || S <- Ciphers], AtomVersion); filter_suites([Cipher | _] = Ciphers, AtomVersion) when is_binary(Cipher)-> - filter_suites([ssl_cipher_format:suite_definition(S) || S <- Ciphers], + filter_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- Ciphers], AtomVersion); filter_suites(Ciphers0, AtomVersion) -> Version = tls_version(AtomVersion), @@ -1927,11 +2269,11 @@ filter_suites(Ciphers0, AtomVersion) -> ++ ssl_cipher:anonymous_suites(Version) ++ ssl_cipher:psk_suites(Version) ++ ssl_cipher:psk_suites_anon(Version) - ++ ssl_cipher:srp_suites() - ++ ssl_cipher:srp_suites_anon() + ++ ssl_cipher:srp_suites(Version) + ++ ssl_cipher:srp_suites_anon(Version) ++ ssl_cipher:rc4_suites(Version), Supported1 = ssl_cipher:filter_suites(Supported0), - Supported2 = [ssl_cipher_format:suite_definition(S) || S <- Supported1], + Supported2 = [ssl_cipher_format:suite_bin_to_map(S) || S <- Supported1], [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported2)]. -define(OPENSSL_QUIT, "Q\n"). @@ -2045,6 +2387,14 @@ protocol_version(Config, atom) -> tls_record:protocol_version(protocol_version(Config, tuple)) end. +protocol_versions(Config) -> + Version = protocol_version(Config), + case Version of + 'tlsv1.3' -> %% TLS-1.3 servers shall also support 1.2 + ['tlsv1.3', 'tlsv1.2']; + _ -> + [Version] + end. protocol_options(Config, Options) -> Protocol = proplists:get_value(protocol, Config, tls), {Protocol, Opts} = lists:keyfind(Protocol, 1, Options), @@ -2068,7 +2418,8 @@ clean_env() -> application:unset_env(ssl, session_cache_server_max), application:unset_env(ssl, ssl_pem_cache_clean), application:unset_env(ssl, bypass_pem_cache), - application:unset_env(ssl, alert_timeout). + application:unset_env(ssl, alert_timeout), + application:unset_env(ssl, internal_active_n). clean_start() -> ssl:stop(), @@ -2342,4 +2693,98 @@ reuse_session(ClientOpts, ServerOpts, Config) -> ssl_test_lib:close(Server1), ssl_test_lib:close(Client3), ssl_test_lib:close(Client4). - + +user_lookup(psk, _Identity, UserState) -> + {ok, UserState}; +user_lookup(srp, Username, _UserState) -> + Salt = ssl_cipher:random_bytes(16), + UserPassHash = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, <<"secret">>])]), + {ok, {srp_1024, Salt, UserPassHash}}. + +test_cipher(TestCase, Config) -> + [{name, Group} |_] = proplists:get_value(tc_group_properties, Config), + list_to_atom(re:replace(atom_to_list(TestCase), atom_to_list(Group) ++ "_", "", [{return, list}])). + +digest() -> + case application:get_env(ssl, protocol_version, application:get_env(ssl, dtls_protocol_version)) of + Ver when Ver == 'tlsv1.2'; + Ver == 'dtlsv1.2' -> + {digest, sha256}; + _ -> + {digest, sha1} + end. + +kill_openssl() -> + case os:type() of + {unix, _} -> + os:cmd("pkill openssl"); + {win32, _} -> + os:cmd("cmd.exe /C \"taskkill /IM openssl.exe /F\"") + end. + +hostname_format(Hostname) -> + case lists:member($., Hostname) of + true -> + Hostname; + false -> + "localhost" + end. + +erlang_ssl_receive_and_assert_negotiated_protocol(Socket, Protocol, Data) -> + case ssl:negotiated_protocol(Socket) of + {ok, Protocol} -> + active_recv(Socket, length(Data)); + Result -> + {error, {{expected, Protocol}, {got, Result}}} + end. + +check_openssl_npn_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "nextprotoneg") of + 0 -> + {skip, "Openssl not compiled with nextprotoneg support"}; + _ -> + Config + end. + +new_config(PrivDir, ServerOpts0) -> + CaCertFile = proplists:get_value(cacertfile, ServerOpts0), + CertFile = proplists:get_value(certfile, ServerOpts0), + KeyFile = proplists:get_value(keyfile, ServerOpts0), + NewCaCertFile = filename:join(PrivDir, "new_ca.pem"), + NewCertFile = filename:join(PrivDir, "new_cert.pem"), + NewKeyFile = filename:join(PrivDir, "new_key.pem"), + file:copy(CaCertFile, NewCaCertFile), + file:copy(CertFile, NewCertFile), + file:copy(KeyFile, NewKeyFile), + ServerOpts1 = proplists:delete(cacertfile, ServerOpts0), + ServerOpts2 = proplists:delete(certfile, ServerOpts1), + ServerOpts = proplists:delete(keyfile, ServerOpts2), + + {ok, PEM} = file:read_file(NewCaCertFile), + ct:log("CA file content: ~p~n", [public_key:pem_decode(PEM)]), + + [{cacertfile, NewCaCertFile}, {certfile, NewCertFile}, + {keyfile, NewKeyFile} | ServerOpts]. + +sane_openssl_alpn_npn_renegotiate() -> + case os:cmd("openssl version") of + "LibreSSL 2.9.1" ++ _ -> + false; + "LibreSSL 2.6.4" ++ _ -> + false; + "OpenSSL 1.1.1a-freebsd" ++ _ -> + false; + _ -> + true + end. + +openssl_sane_dtls_alpn() -> + case os:cmd("openssl version") of + "OpenSSL 1.1.0g" ++ _ -> + false; + "OpenSSL 1.1.1a" ++ _ -> + false; + _-> + openssl_sane_dtls() + end. diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl deleted file mode 100644 index df84411b6d..0000000000 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ /dev/null @@ -1,2116 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2018. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% -%% - --module(ssl_to_openssl_SUITE). - -%% Note: This directive should only be used in test suites. --compile(export_all). - --include_lib("common_test/include/ct.hrl"). - --define(SLEEP, 1000). --define(OPENSSL_RENEGOTIATE, "R\n"). --define(OPENSSL_QUIT, "Q\n"). --define(OPENSSL_GARBAGE, "P\n"). --define(EXPIRE, 10). - -%%-------------------------------------------------------------------- -%% Common Test interface functions ----------------------------------- -%%-------------------------------------------------------------------- - -all() -> - case ssl_test_lib:openssl_sane_dtls() of - true -> - [{group, basic}, - {group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'sslv3'}, - {group, 'dtlsv1.2'}, - {group, 'dtlsv1'}]; - false -> - [{group, basic}, - {group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'sslv3'}] - end. - -groups() -> - case ssl_test_lib:openssl_sane_dtls() of - true -> - [{basic, [], basic_tests()}, - {'tlsv1.2', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'tlsv1.1', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'tlsv1', [], all_versions_tests()++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'sslv3', [], all_versions_tests()}, - {'dtlsv1.2', [], dtls_all_versions_tests()}, - {'dtlsv1', [], dtls_all_versions_tests()} - ]; - false -> - [{basic, [], basic_tests()}, - {'tlsv1.2', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'tlsv1.1', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'tlsv1', [], all_versions_tests()++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'sslv3', [], all_versions_tests()} - ] - end. - -basic_tests() -> - [basic_erlang_client_openssl_server, - basic_erlang_server_openssl_client, - expired_session - ]. - -all_versions_tests() -> - [ - erlang_client_openssl_server, - erlang_server_openssl_client, - erlang_client_openssl_server_dsa_cert, - erlang_server_openssl_client_dsa_cert, - erlang_client_openssl_server_anon, - erlang_server_openssl_client_anon, - erlang_server_openssl_client_anon_with_cert, - erlang_server_openssl_client_reuse_session, - erlang_client_openssl_server_renegotiate, - erlang_client_openssl_server_renegotiate_after_client_data, - erlang_client_openssl_server_nowrap_seqnum, - erlang_server_openssl_client_nowrap_seqnum, - erlang_client_openssl_server_no_server_ca_cert, - erlang_client_openssl_server_client_cert, - erlang_server_openssl_client_client_cert, - ciphers_rsa_signed_certs, - ciphers_dsa_signed_certs, - erlang_client_bad_openssl_server, - expired_session, - ssl2_erlang_server_openssl_client - ]. - -dtls_all_versions_tests() -> - case ssl_test_lib:openssl_sane_client_cert() of - true -> - [erlang_server_openssl_client_client_cert, - erlang_client_openssl_server_no_server_ca_cert, - erlang_client_openssl_server_client_cert - | dtls_all_versions_tests_2()]; - false -> - dtls_all_versions_tests_2() - end. - -dtls_all_versions_tests_2() -> - [erlang_client_openssl_server, - erlang_server_openssl_client, - erlang_client_openssl_server_dsa_cert, - erlang_server_openssl_client_dsa_cert, - erlang_client_openssl_server_anon, - erlang_server_openssl_client_anon, - erlang_server_openssl_client_anon_with_cert, - erlang_server_openssl_client_reuse_session, - erlang_client_openssl_server_renegotiate, - erlang_client_openssl_server_nowrap_seqnum, - erlang_server_openssl_client_nowrap_seqnum, - ciphers_rsa_signed_certs, - ciphers_dsa_signed_certs - %%expired_session - ]. - -alpn_tests() -> - [erlang_client_alpn_openssl_server_alpn, - erlang_server_alpn_openssl_client_alpn, - erlang_client_alpn_openssl_server, - erlang_client_openssl_server_alpn, - erlang_server_alpn_openssl_client, - erlang_server_openssl_client_alpn, - erlang_client_alpn_openssl_server_alpn_renegotiate, - erlang_server_alpn_openssl_client_alpn_renegotiate, - erlang_client_alpn_npn_openssl_server_alpn_npn, - erlang_server_alpn_npn_openssl_client_alpn_npn]. - -npn_tests() -> - [erlang_client_openssl_server_npn, - erlang_server_openssl_client_npn, - erlang_server_openssl_client_npn_renegotiate, - erlang_client_openssl_server_npn_renegotiate, - erlang_server_openssl_client_npn_only_client, - erlang_server_openssl_client_npn_only_server, - erlang_client_openssl_server_npn_only_client, - erlang_client_openssl_server_npn_only_server]. - -sni_server_tests() -> - [erlang_server_openssl_client_sni_match, - erlang_server_openssl_client_sni_match_fun, - erlang_server_openssl_client_sni_no_match, - erlang_server_openssl_client_sni_no_match_fun, - erlang_server_openssl_client_sni_no_header, - erlang_server_openssl_client_sni_no_header_fun]. - - -init_per_suite(Config0) -> - case os:find_executable("openssl") of - false -> - {skip, "Openssl not found"}; - _ -> - ct:pal("Version: ~p", [os:cmd("openssl version")]), - catch crypto:stop(), - try crypto:start() of - ok -> - ssl_test_lib:clean_start(), - Config = - case ssl_test_lib:openssl_dsa_support() of - true -> - Config1 = ssl_test_lib:make_rsa_cert(Config0), - ssl_test_lib:make_dsa_cert(Config1); - false -> - ssl_test_lib:make_rsa_cert(Config0) - end, - ssl_test_lib:cipher_restriction(Config) - catch _:_ -> - {skip, "Crypto did not start"} - end - end. - -end_per_suite(_Config) -> - ssl:stop(), - application:stop(crypto). - -init_per_group(basic, Config0) -> - case ssl_test_lib:supports_ssl_tls_version('tlsv1.2') - orelse ssl_test_lib:supports_ssl_tls_version('tlsv1.1') - orelse ssl_test_lib:supports_ssl_tls_version('tlsv1') - of - true -> - ssl_test_lib:clean_tls_version(Config0); - false -> - {skip, "only sslv3 supported by OpenSSL"} - end; - -init_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - case ssl_test_lib:supports_ssl_tls_version(GroupName) of - true -> - case ssl_test_lib:check_sane_openssl_version(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - false -> - {skip, openssl_does_not_support_version} - end; - false -> - {skip, openssl_does_not_support_version} - end; - _ -> - ssl:start(), - Config - end. - -end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. - -init_per_testcase(expired_session, Config) -> - ct:timetrap(?EXPIRE * 1000 * 5), - ssl:stop(), - application:load(ssl), - application:set_env(ssl, session_lifetime, ?EXPIRE), - ssl:start(), - Config; - -init_per_testcase(TestCase, Config) when - TestCase == ciphers_dsa_signed_certs; - TestCase == erlang_client_openssl_server_dsa_cert; - TestCase == erlang_server_openssl_client_dsa_cert; - TestCase == erlang_client_openssl_server_dsa_cert; - TestCase == erlang_server_openssl_client_dsa_cert -> - case ssl_test_lib:openssl_dsa_support() of - true -> - special_init(TestCase, Config); - false -> - {skip, "DSA not supported by OpenSSL"} - end; -init_per_testcase(TestCase, Config) -> - ct:timetrap({seconds, 35}), - special_init(TestCase, Config). - -special_init(TestCase, Config) when - TestCase == ciphers_rsa_signed_certs; - TestCase == ciphers_dsa_signed_certs-> - ct:timetrap({seconds, 90}), - Config; -special_init(TestCase, Config) - when TestCase == erlang_client_openssl_server_renegotiate; - TestCase == erlang_client_openssl_server_nowrap_seqnum; - TestCase == erlang_server_openssl_client_nowrap_seqnum; - TestCase == erlang_client_openssl_server_renegotiate_after_client_data - -> - {ok, Version} = application:get_env(ssl, protocol_version), - check_sane_openssl_renegotaite(Config, Version); - -special_init(ssl2_erlang_server_openssl_client, Config) -> - case ssl_test_lib:supports_ssl_tls_version(sslv2) of - true -> - Config; - false -> - {skip, "sslv2 not supported by openssl"} - end; - -special_init(TestCase, Config) - when TestCase == erlang_client_alpn_openssl_server_alpn; - TestCase == erlang_server_alpn_openssl_client_alpn; - TestCase == erlang_client_alpn_openssl_server; - TestCase == erlang_client_openssl_server_alpn; - TestCase == erlang_server_alpn_openssl_client; - TestCase == erlang_server_openssl_client_alpn -> - check_openssl_alpn_support(Config); - -special_init(TestCase, Config) - when TestCase == erlang_client_alpn_openssl_server_alpn_renegotiate; - TestCase == erlang_server_alpn_openssl_client_alpn_renegotiate -> - {ok, Version} = application:get_env(ssl, protocol_version), - case check_sane_openssl_renegotaite(Config, Version) of - {skip, _} = Skip -> - Skip; - _ -> - check_openssl_alpn_support(Config) - end; - -special_init(TestCase, Config) - when TestCase == erlang_client_alpn_npn_openssl_server_alpn_npn; - TestCase == erlang_server_alpn_npn_openssl_client_alpn_npn -> - case check_openssl_alpn_support(Config) of - {skip, _} = Skip -> - Skip; - _ -> - check_openssl_npn_support(Config) - end; - -special_init(TestCase, Config) - when TestCase == erlang_client_openssl_server_npn; - TestCase == erlang_server_openssl_client_npn; - TestCase == erlang_server_openssl_client_npn_only_server; - TestCase == erlang_server_openssl_client_npn_only_client; - TestCase == erlang_client_openssl_server_npn_only_client; - TestCase == erlang_client_openssl_server_npn_only_server -> - check_openssl_npn_support(Config); - -special_init(TestCase, Config) - when TestCase == erlang_server_openssl_client_npn_renegotiate; - TestCase == erlang_client_openssl_server_npn_renegotiate -> - {ok, Version} = application:get_env(ssl, protocol_version), - case check_sane_openssl_renegotaite(Config, Version) of - {skip, _} = Skip -> - Skip; - _ -> - check_openssl_npn_support(Config) - end; - -special_init(TestCase, Config0) - when TestCase == erlang_server_openssl_client_sni_match; - TestCase == erlang_server_openssl_client_sni_no_match; - TestCase == erlang_server_openssl_client_sni_no_header; - TestCase == erlang_server_openssl_client_sni_match_fun; - TestCase == erlang_server_openssl_client_sni_no_match_fun; - TestCase == erlang_server_openssl_client_sni_no_header_fun -> - RsaOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config0), - Config = [{sni_server_opts, [{sni_hosts, - [{"a.server", [ - {certfile, proplists:get_value(certfile, RsaOpts)}, - {keyfile, proplists:get_value(keyfile, RsaOpts)} - ]}, - {"b.server", [ - {certfile, proplists:get_value(certfile, RsaOpts)}, - {keyfile, proplists:get_value(keyfile, RsaOpts)} - ]} - ]}]} | Config0], - check_openssl_sni_support(Config); - -special_init(_, Config) -> - Config. - -end_per_testcase(reuse_session_expired, Config) -> - application:unset_env(ssl, session_lifetime), - Config; -end_per_testcase(_, Config) -> - Config. - -%%-------------------------------------------------------------------- -%% Test Cases -------------------------------------------------------- -%%-------------------------------------------------------------------- -basic_erlang_client_openssl_server() -> - [{doc,"Test erlang client with openssl server"}]. -basic_erlang_client_openssl_server(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - "-cert", CertFile, "-key", KeyFile], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - - ssl_test_lib:wait_for_openssl_server(Port, tls), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -basic_erlang_server_openssl_client() -> - [{doc,"Test erlang server with openssl client"}]. -basic_erlang_server_openssl_client(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options,ServerOpts}]), - - Port = ssl_test_lib:inet_port(Server), - - Exe = "openssl", - Args = case no_low_flag("-no_ssl2") of - [] -> - ["s_client", "-connect", hostname_format(Hostname) ++ - ":" ++ integer_to_list(Port), no_low_flag("-no_ssl3") - | workaround_openssl_s_clinent()]; - Flag -> - ["s_client", "-connect", hostname_format(Hostname) ++ - ":" ++ integer_to_list(Port), no_low_flag("-no_ssl3"), Flag - | workaround_openssl_s_clinent()] - end, - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -erlang_client_openssl_server() -> - [{doc,"Test erlang client with openssl server"}]. -erlang_client_openssl_server(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -erlang_server_openssl_client() -> - [{doc,"Test erlang server with openssl client"}]. -erlang_server_openssl_client(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version)], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -erlang_client_openssl_server_dsa_cert() -> - [{doc,"Test erlang server with openssl client"}]. -erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-CAfile", CaCertFile, - "-key", KeyFile, "-Verify", "2", "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false), - ok. -%%-------------------------------------------------------------------- -erlang_server_openssl_client_dsa_cert() -> - [{doc,"Test erlang server with openssl client"}]. -erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - CaCertFile = proplists:get_value(cacertfile, ClientOpts), - CertFile = proplists:get_value(certfile, ClientOpts), - KeyFile = proplists:get_value(keyfile, ClientOpts), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, - "-CAfile", CaCertFile, - "-key", KeyFile, "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - - %%-------------------------------------------------------------------- -erlang_client_openssl_server_anon() -> - [{doc,"Test erlang client with openssl server, anonymous"}]. -erlang_client_openssl_server_anon(Config) when is_list(Config) -> - process_flag(trap_exit, true), - %% OpenSSL expects a certificate and key, even if the cipher spec - %% is restructed to aNULL, so we use 'server_rsa_opts' here - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_anon_opts, Config), - VersionTuple = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(VersionTuple), - - case openssl_has_common_ciphers(Ciphers) of - false -> - {skip, not_supported_by_openssl}; - true -> - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile, - "-cipher", "aNULL", "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, [{ciphers, Ciphers} | ClientOpts]}]), - - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false) - end. -%%-------------------------------------------------------------------- -erlang_server_openssl_client_anon() -> - [{doc,"Test erlang server with openssl client, anonymous"}]. -erlang_server_openssl_client_anon(Config) when is_list(Config) -> - - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_anon_opts, Config), - VersionTuple = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(VersionTuple), - - case openssl_has_common_ciphers(Ciphers) of - false -> - {skip, not_supported_by_openssl}; - true -> - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{ciphers, Ciphers} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cipher", "aNULL", "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false) - end. - -%%-------------------------------------------------------------------- -erlang_server_openssl_client_anon_with_cert() -> - [{doc,"Test erlang server with openssl client, anonymous (with cert)"}]. -erlang_server_openssl_client_anon_with_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - VersionTuple = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(VersionTuple), - - case openssl_has_common_ciphers(Ciphers) of - false -> - {skip, not_supported_by_openssl}; - true -> - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{ciphers, Ciphers} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cipher", "aNULL", "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false) - end. - - %%-------------------------------------------------------------------- -erlang_server_openssl_client_reuse_session() -> - [{doc, "Test erlang server with openssl client that reconnects with the" - "same session id, to test reusing of sessions."}]. -erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {reconnect_times, 5}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) - ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-reconnect"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_openssl_server_renegotiate() -> - [{doc,"Test erlang client when openssl server issuses a renegotiate"}]. -erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to openssl", - OpenSslData = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile, "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - delayed_send, [[ErlData, OpenSslData]]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, OpenSslData), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false), - ok. -%%-------------------------------------------------------------------- -erlang_client_openssl_server_renegotiate_after_client_data() -> - [{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}]. -erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to openssl", - OpenSslData = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile, "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - send_wait_send, [[ErlData, OpenSslData]]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, OpenSslData), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_openssl_server_nowrap_seqnum() -> - [{doc, "Test that erlang client will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. -erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to openssl\n", - N = 10, - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile, "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[ErlData, N+2]]}}, - {options, [{reuse_sessions, false}, - {renegotiate_at, N} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). -%%-------------------------------------------------------------------- -erlang_server_openssl_client_nowrap_seqnum() -> - [{doc, "Test that erlang client will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. -erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - N = 10, - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[Data, N+2]]}}, - {options, [{renegotiate_at, N}, {reuse_sessions, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client","-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- - -erlang_client_openssl_server_no_server_ca_cert() -> - [{doc, "Test erlang client when openssl server sends a cert chain not" - "including the ca cert. Explicitly test this even if it is" - "implicitly tested eleswhere."}]. -erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile, "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -erlang_client_openssl_server_client_cert() -> - [{doc,"Test erlang client with openssl server when client sends cert"}]. -erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-CAfile", CaCertFile, - "-key", KeyFile, "-Verify", "2"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -erlang_server_openssl_client_client_cert() -> - [{doc,"Test erlang server with openssl client when client sends cert"}]. -erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, - [{verify , verify_peer} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - CaCertFile = proplists:get_value(cacertfile, ClientOpts), - CertFile = proplists:get_value(certfile, ClientOpts), - KeyFile = proplists:get_value(keyfile, ClientOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-cert", CertFile, - "-CAfile", CaCertFile, - "-key", KeyFile,"-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version)], - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpenSslPort), - ssl_test_lib:close(Server), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -erlang_server_erlang_client_client_cert() -> - [{doc,"Test erlang server with erlang client when client sends cert"}]. -erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), - ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), - Version = ssl_test_lib:protocol_version(Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, - %% Due to 1/n-1 splitting countermeasure Rizzo/Duong-Beast - [Data]}}, - {options, - [{verify , verify_peer} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - %% Due to 1/n-1 splitting countermeasure Rizzo/Duong-Beast - {mfa, {ssl, send, [Data]}}, - {options, - [{versions, [Version]} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- - -ciphers_rsa_signed_certs() -> - [{doc,"Test cipher suites that uses rsa certs"}]. -ciphers_rsa_signed_certs(Config) when is_list(Config) -> - Version = ssl_test_lib:protocol_version(Config), - Ciphers = ssl_test_lib:rsa_suites(openssl), - run_suites(Ciphers, Version, Config, rsa). -%%-------------------------------------------------------------------- - -ciphers_dsa_signed_certs() -> - [{doc,"Test cipher suites that uses dsa certs"}]. -ciphers_dsa_signed_certs(Config) when is_list(Config) -> - Version = ssl_test_lib:protocol_version(Config), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:dsa_suites(NVersion), - run_suites(Ciphers, Version, Config, dsa). - -%%-------------------------------------------------------------------- -erlang_client_bad_openssl_server() -> - [{doc,"Test what happens if openssl server sends garbage to erlang ssl client"}]. -erlang_client_bad_openssl_server(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, server_sent_garbage, []}}, - {options, - [{versions, [Version]} | ClientOpts]}]), - - %% Send garbage - true = port_command(OpensslPort, ?OPENSSL_GARBAGE), - - ct:sleep(?SLEEP), - - Client0 ! server_sent_garbage, - - ssl_test_lib:check_result(Client0, true), - - ssl_test_lib:close(Client0), - - %% Make sure openssl does not hang and leave zombie process - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result_msg, []}}, - {options, - [{versions, [Version]} | ClientOpts]}]), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client1), - process_flag(trap_exit, false), - ok. - -%%-------------------------------------------------------------------- - -expired_session() -> - [{doc, "Test our ssl client handling of expired sessions. Will make" - "better code coverage of the ssl_manager module"}]. -expired_session(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - "-cert", CertFile,"-key", KeyFile], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, tls), - - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - ssl_test_lib:close(Client0), - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - ssl_test_lib:close(Client1), - %% Make sure session is unregistered due to expiration - ct:sleep((?EXPIRE+1) * 1000), - - Client2 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client2), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -ssl2_erlang_server_openssl_client() -> - [{doc,"Test that ssl v2 clients are rejected"}]. - -ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - "-ssl2", "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), - ssl_test_lib:consume_port_exit(OpenSslPort), - ssl_test_lib:check_server_alert(Server, bad_record_mac), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- - -erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_client_alpn_openssl_server(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, - [{alpn_advertised_protocols, [<<"spdy/2">>]}], - [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_client_openssl_server_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, - [], - ["-alpn", "spdy/2"], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_server_alpn_openssl_client(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, - [{alpn_preferred_protocols, [<<"spdy/2">>]}], - [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_server_openssl_client_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, - [], - ["-alpn", "spdy/2"], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_alpn_openssl_server_alpn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_server_alpn_openssl_client_alpn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_server_alpn_npn_openssl_client_alpn_npn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- -erlang_client_openssl_server_npn() -> - [{doc,"Test erlang client with openssl server doing npn negotiation"}]. - -erlang_client_openssl_server_npn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- -erlang_client_openssl_server_npn_renegotiate() -> - [{doc,"Test erlang client with openssl server doing npn negotiation and renegotiate"}]. - -erlang_client_openssl_server_npn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Client, ok) - end), - ok. -%%-------------------------------------------------------------------------- -erlang_server_openssl_client_npn() -> - [{doc,"Test erlang server with openssl client and npn negotiation"}]. - -erlang_server_openssl_client_npn(Config) when is_list(Config) -> - - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- -erlang_server_openssl_client_npn_renegotiate() -> - [{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}]. - -erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. -%%-------------------------------------------------------------------------- -erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, [], - ["-nextprotoneg", "spdy/2"], Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, - [{client_preferred_next_protocols, - {client, [<<"spdy/2">>], <<"http/1.1">>}}], [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- -erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, [{next_protocols_advertised, [<<"spdy/2">>]}], [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, [], ["-nextprotoneg", "spdy/2"], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. -%-------------------------------------------------------------------------- -erlang_server_openssl_client_sni_no_header(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test(Config, undefined, undefined, "server Peer cert"). - -erlang_server_openssl_client_sni_no_header_fun(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test_sni_fun(Config, undefined, undefined, "server Peer cert"). - -erlang_server_openssl_client_sni_match(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test(Config, "a.server", "a.server", "server Peer cert"). - -erlang_server_openssl_client_sni_match_fun(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test_sni_fun(Config, "a.server", "a.server", "server Peer cert"). - -erlang_server_openssl_client_sni_no_match(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test(Config, "c.server", undefined, "server Peer cert"). - -erlang_server_openssl_client_sni_no_match_fun(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test_sni_fun(Config, "c.server", undefined, "server Peer cert"). - - -%%-------------------------------------------------------------------- -%% Internal functions ------------------------------------------------ -%%-------------------------------------------------------------------- -run_suites(Ciphers, Version, Config, Type) -> - {ClientOpts, ServerOpts} = - case Type of - rsa -> - {ssl_test_lib:ssl_options(client_rsa_opts, Config), - ssl_test_lib:ssl_options(server_rsa_opts, Config)}; - dsa -> - {ssl_test_lib:ssl_options(client_dsa_opts, Config), - ssl_test_lib:ssl_options(server_dsa_verify_opts, Config)} - end, - - Result = lists:map(fun(Cipher) -> - cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end, - Ciphers), - case lists:flatten(Result) of - [] -> - ok; - Error -> - ct:log("Cipher suite errors: ~p~n", [Error]), - ct:fail(cipher_suite_failed_see_test_case_log) - end. - -client_read_check([], _Data) -> - ok; -client_read_check([Hd | T], Data) -> - case binary:match(Data, list_to_binary(Hd)) of - nomatch -> - nomatch; - _ -> - client_read_check(T, Data) - end. -client_check_result(Port, DataExpected, DataReceived) -> - receive - {Port, {data, TheData}} -> - Data = list_to_binary(TheData), - NewData = <<DataReceived/binary, Data/binary>>, - ct:log("New Data: ~p", [NewData]), - case client_read_check(DataExpected, NewData) of - ok -> - ok; - _ -> - client_check_result(Port, DataExpected, NewData) - end - after 20000 -> - ct:fail({"Time out on openSSL Client", {expected, DataExpected}, - {got, DataReceived}}) - end. -client_check_result(Port, DataExpected) -> - client_check_result(Port, DataExpected, <<"">>). - -send_and_hostname(SSLSocket) -> - ssl:send(SSLSocket, "OK"), - case ssl:connection_information(SSLSocket, [sni_hostname]) of - {ok, []} -> - undefined; - {ok, [{sni_hostname, Hostname}]} -> - Hostname - end. - -erlang_server_openssl_client_sni_test(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> - ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), - ServerOptions = proplists:get_value(sni_server_opts, Config) ++ proplists:get_value(server_rsa_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, - {options, ServerOptions}]), - Port = ssl_test_lib:inet_port(Server), - Exe = "openssl", - ClientArgs = case SNIHostname of - undefined -> - openssl_client_args(ssl_test_lib:supports_ssl_tls_version(sslv2), Hostname,Port); - _ -> - openssl_client_args(ssl_test_lib:supports_ssl_tls_version(sslv2), Hostname, Port, SNIHostname) - end, - ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), - - ssl_test_lib:check_result(Server, ExpectedSNIHostname), - ssl_test_lib:close_port(ClientPort), - ssl_test_lib:close(Server), - ok. - - -erlang_server_openssl_client_sni_test_sni_fun(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> - ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), - [{sni_hosts, ServerSNIConf}] = proplists:get_value(sni_server_opts, Config), - SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end, - ServerOptions = proplists:get_value(server_rsa_opts, Config) ++ [{sni_fun, SNIFun}], - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, - {options, ServerOptions}]), - Port = ssl_test_lib:inet_port(Server), - Exe = "openssl", - ClientArgs = case SNIHostname of - undefined -> - openssl_client_args(ssl_test_lib:supports_ssl_tls_version(sslv2), Hostname,Port); - _ -> - openssl_client_args(ssl_test_lib:supports_ssl_tls_version(sslv2), Hostname, Port, SNIHostname) - end, - - ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), - - ssl_test_lib:check_result(Server, ExpectedSNIHostname), - ssl_test_lib:close_port(ClientPort), - ssl_test_lib:close(Server). - - -cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> - process_flag(trap_exit, true), - ct:log("Testing CipherSuite ~p~n", [CipherSuite]), - {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - ConnectionInfo = {ok, {Version, CipherSuite}}, - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, cipher_result, [ConnectionInfo]}}, - {options, - [{ciphers,[CipherSuite]} | - ClientOpts]}]), - - true = port_command(OpenSslPort, "Hello\n"), - - receive - {Port, {data, _}} when is_port(Port) -> - ok - after 500 -> - ct:log("Time out on openssl port, check that" - " the messages Hello and world are received" - " during close of port" , []), - ok - end, - - true = port_command(OpenSslPort, " world\n"), - - Result = ssl_test_lib:wait_for_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpenSslPort), - ssl_test_lib:close(Client), - - Return = case Result of - ok -> - []; - Error -> - [{CipherSuite, Error}] - end, - process_flag(trap_exit, false), - Return. - -start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ClientOpts = ErlangClientOpts ++ ClientOpts0, - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = case OpensslServerOpts of - [] -> - ["s_server", "-accept", - integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile,"-key", KeyFile]; - [Opt, Value] -> - ["s_server", Opt, Value, "-accept", - integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile,"-key", KeyFile] - end, - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), - ClientOpts0 = proplists:get_value(client_rsa_verify_opts, Config), - ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0], - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile], - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = proplists:get_value(server_rsa_opts, Config), - ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]} | ServerOpts0], - - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-alpn", "http/1.0,spdy/2", "-msg", "-port", - integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-host", "localhost"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - Callback(Server, OpenSslPort), - - ssl_test_lib:close(Server), - - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_opts, Config), - ClientOpts0 = proplists:get_value(client_rsa_opts, Config), - ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]}, - {client_preferred_next_protocols, {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts0], - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", - "spdy/3", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = proplists:get_value(server_rsa_opts, Config), - ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]}, - {next_protocols_advertised, [<<"spdy/3">>, <<"http/1.1">>]} | ServerOpts0], - - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", "spdy/3", - "-msg", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-host", "localhost"], - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - Callback(Server, OpenSslPort), - - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0], - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile], - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]}, ServerOpts0], - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-nextprotoneg", "http/1.0,spdy/2", "-msg", "-connect", - hostname_format(Hostname) ++ ":" - ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - Callback(Server, OpenSslPort), - - ssl_test_lib:close(Server), - - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - - -start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenSSLClientOpts, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ServerOpts = ErlangServerOpts ++ ServerOpts0, - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client"] ++ OpenSSLClientOpts ++ ["-msg", "-connect", - hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version)], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - Callback(Server, OpenSslPort), - - ssl_test_lib:close(Server), - - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - - -erlang_ssl_receive_and_assert_negotiated_protocol(Socket, Protocol, Data) -> - {ok, Protocol} = ssl:negotiated_protocol(Socket), - erlang_ssl_receive(Socket, Data), - {ok, Protocol} = ssl:negotiated_protocol(Socket), - ok. - -erlang_ssl_receive(Socket, Data) -> - ct:log("Connection info: ~p~n", - [ssl:connection_information(Socket)]), - receive - {ssl, Socket, "R\n"} -> - %% Swallow s_client renegotiation command. - %% openssl s_client connected commands can appear on - %% server side with some openssl versions. - erlang_ssl_receive(Socket,Data); - {ssl, Socket, Data} -> - io:format("Received ~p~n",[Data]), - %% open_ssl server sometimes hangs waiting in blocking read - ssl:send(Socket, "Got it"), - ok; - {ssl, Socket, Byte} when length(Byte) == 1 -> - erlang_ssl_receive(Socket, tl(Data)); - {Port, {data,Debug}} when is_port(Port) -> - io:format("openssl ~s~n",[Debug]), - erlang_ssl_receive(Socket,Data); - Other -> - ct:fail({unexpected_message, Other}) - end. - -connection_info(Socket, Version) -> - case ssl:connection_information(Socket, [version]) of - {ok, [{version, Version}] = Info} -> - ct:log("Connection info: ~p~n", [Info]), - ok; - {ok, [{version, OtherVersion}]} -> - {wrong_version, OtherVersion} - end. - -connection_info_result(Socket) -> - ssl:connection_information(Socket). - - -delayed_send(Socket, [ErlData, OpenSslData]) -> - ct:sleep(?SLEEP), - ssl:send(Socket, ErlData), - erlang_ssl_receive(Socket, OpenSslData). - -server_sent_garbage(Socket) -> - receive - server_sent_garbage -> - {error, closed} == ssl:send(Socket, "data") - - end. - -send_wait_send(Socket, [ErlData, OpenSslData]) -> - ssl:send(Socket, ErlData), - ct:sleep(?SLEEP), - ssl:send(Socket, ErlData), - erlang_ssl_receive(Socket, OpenSslData). - -check_openssl_sni_support(Config) -> - HelpText = os:cmd("openssl s_client --help"), - case string:str(HelpText, "-servername") of - 0 -> - {skip, "Current openssl doesn't support SNI"}; - _ -> - Config - end. - -check_openssl_npn_support(Config) -> - HelpText = os:cmd("openssl s_client --help"), - case string:str(HelpText, "nextprotoneg") of - 0 -> - {skip, "Openssl not compiled with nextprotoneg support"}; - _ -> - Config - end. - -check_openssl_alpn_support(Config) -> - HelpText = os:cmd("openssl s_client --help"), - case string:str(HelpText, "alpn") of - 0 -> - {skip, "Openssl not compiled with alpn support"}; - _ -> - Config - end. - -check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1.1'; - Version == 'tlsv1.2' -> - case os:cmd("openssl version") of - "OpenSSL 1.0.1c" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - "OpenSSL 1.0.1b" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - "OpenSSL 1.0.1a" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - "OpenSSL 1.0.1 " ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - _ -> - check_sane_openssl_renegotaite(Config) - end; -check_sane_openssl_renegotaite(Config, _) -> - check_sane_openssl_renegotaite(Config). - -check_sane_openssl_renegotaite(Config) -> - case os:cmd("openssl version") of - "OpenSSL 1.0.0" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - "OpenSSL 0.9.8" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - "OpenSSL 0.9.7" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - _ -> - Config - end. - -workaround_openssl_s_clinent() -> - %% http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=683159 - %% https://bugs.archlinux.org/task/33919 - %% Bug seems to manifests it self if TLS version is not - %% explicitly specified - case os:cmd("openssl version") of - "OpenSSL 1.0.1c" ++ _ -> - ["-no_tls1_2"]; - "OpenSSL 1.0.1d" ++ _ -> - ["-no_tls1_2"]; - "OpenSSL 1.0.1e" ++ _ -> - ["-no_tls1_2"]; - "OpenSSL 1.0.1f" ++ _ -> - ["-no_tls1_2"]; - _ -> - [] - end. - -openssl_client_args(false, Hostname, Port) -> - ["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port)]; -openssl_client_args(true, Hostname, Port) -> - ["s_client", "-no_ssl2", "-connect", Hostname ++ ":" ++ integer_to_list(Port)]. - -openssl_client_args(false, Hostname, Port, ServerName) -> - ["s_client", "-connect", Hostname ++ ":" ++ - integer_to_list(Port), "-servername", ServerName]; -openssl_client_args(true, Hostname, Port, ServerName) -> - ["s_client", "-no_ssl2", "-connect", Hostname ++ ":" ++ - integer_to_list(Port), "-servername", ServerName]. - -hostname_format(Hostname) -> - case lists:member($., Hostname) of - true -> - Hostname; - false -> - "localhost" - end. - -no_low_flag("-no_ssl2" = Flag) -> - case ssl_test_lib:supports_ssl_tls_version(sslv2) of - true -> - Flag; - false -> - "" - end; -no_low_flag(Flag) -> - Flag. - - -openssl_has_common_ciphers(Ciphers) -> - OCiphers = ssl_test_lib:common_ciphers(openssl), - has_common_ciphers(Ciphers, OCiphers). - -has_common_ciphers([], OCiphers) -> - false; -has_common_ciphers([Cipher | Rest], OCiphers) -> - case lists:member(Cipher, OCiphers) of - true -> - true; - _ -> - has_common_ciphers(Rest, OCiphers) - end. diff --git a/lib/ssl/test/ssl_upgrade_SUITE.erl b/lib/ssl/test/ssl_upgrade_SUITE.erl index 875399db76..ead18aeb73 100644 --- a/lib/ssl/test/ssl_upgrade_SUITE.erl +++ b/lib/ssl/test/ssl_upgrade_SUITE.erl @@ -47,10 +47,7 @@ init_per_suite(Config0) -> {skip, Reason} -> {skip, Reason}; Config -> - Result = - {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), - proplists:get_value(priv_dir, Config)), - ssl_test_lib:cert_options(Config) + ssl_test_lib:make_rsa_cert(Config) end catch _:_ -> {skip, "Crypto did not start"} @@ -149,8 +146,8 @@ use_connection(Socket) -> end. soft_start_connection(Config, ResulProxy) -> - ClientOpts = proplists:get_value(client_verification_opts, Config), - ServerOpts = proplists:get_value(server_verification_opts, Config), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = start_server([{node, ServerNode}, {port, 0}, {from, ResulProxy}, @@ -166,8 +163,8 @@ soft_start_connection(Config, ResulProxy) -> {Server, Client}. restart_start_connection(Config, ResulProxy) -> - ClientOpts = proplists:get_value(client_verification_opts, Config), - ServerOpts = proplists:get_value(server_verification_opts, Config), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = start_server([{node, ServerNode}, {port, 0}, {from, ResulProxy}, diff --git a/lib/ssl/test/tls_1_3_record_SUITE.erl b/lib/ssl/test/tls_1_3_record_SUITE.erl new file mode 100644 index 0000000000..5df8853b1b --- /dev/null +++ b/lib/ssl/test/tls_1_3_record_SUITE.erl @@ -0,0 +1,973 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(tls_1_3_record_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/tls_record.hrl"). +-include_lib("ssl/src/tls_handshake.hrl"). +-include_lib("ssl/src/ssl_cipher.hrl"). +-include_lib("ssl/src/ssl_internal.hrl"). + +all() -> + [encode_decode, + finished_verify_data, + '1_RTT_handshake']. + +init_per_suite(Config) -> + catch crypto:stop(), + try (ok == crypto:start()) andalso ssl_test_lib:sufficient_crypto_support('tlsv1.3') of + true -> + ssl_test_lib:clean_start(), + Config; + false -> + {skip, "Not enough crypto support for TLS-1.3"} + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +encode_decode() -> + [{doc,"Test TLS 1.3 record encode/decode functions"}]. + +encode_decode(_Config) -> + ConnectionStates = + #{current_read => + #{beast_mitigation => one_n_minus_one, + cipher_state => + {cipher_state, + <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, + 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, + <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, + 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, + undefined,undefined,undefined,16}, + client_verify_data => undefined,compression_state => undefined, + mac_secret => undefined,secure_renegotiation => undefined, + security_parameters => + {security_parameters, + <<19,2>>, + 0,8,2,undefined,undefined,undefined,undefined,undefined, + sha384,undefined,undefined, + {handshake_secret, + <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, + 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, + 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, + 157>>}, + undefined, + <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, + 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, + undefined}, + sequence_number => 0,server_verify_data => undefined}, + current_write => + #{beast_mitigation => one_n_minus_one, + cipher_state => + {cipher_state, + <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, + 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, + <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, + 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, + undefined,undefined,undefined,16}, + client_verify_data => undefined,compression_state => undefined, + mac_secret => undefined,secure_renegotiation => undefined, + security_parameters => + {security_parameters, + <<19,2>>, + 0,8,2,undefined,undefined,undefined,undefined,undefined, + sha384,undefined,undefined, + {handshake_secret, + <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, + 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, + 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, + 157>>}, + undefined, + <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, + 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, + undefined}, + sequence_number => 0,server_verify_data => undefined}}, + + PlainText = [11, + <<0,2,175>>, + <<0,0,2,171,0,2,166,48,130,2,162,48,130,1,138,2,9,0,186,57,220,137,88,255, + 191,235,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,18,49,16,48,14,6,3,85, + 4,3,12,7,84,101,115,116,32,67,65,48,30,23,13,49,56,48,53,48,52,49,52,49,50, + 51,56,90,23,13,50,56,48,50,48,52,49,52,49,50,51,56,90,48,20,49,18,48,16,6, + 3,85,4,3,12,9,108,111,99,97,108,104,111,115,116,48,130,1,34,48,13,6,9,42, + 134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,169,40, + 144,176,121,63,134,97,144,126,243,183,225,157,37,131,183,225,87,243,23,88, + 230,70,9,134,32,147,7,27,167,98,51,81,224,75,199,12,229,251,195,207,75,179, + 181,78,128,3,255,44,58,39,43,172,142,45,186,58,51,65,187,199,154,153,245, + 70,133,137,1,27,87,42,116,65,251,129,109,145,233,97,171,71,54,213,185,74, + 209,166,11,218,189,119,206,86,170,60,212,213,85,189,30,50,215,23,185,53, + 132,238,132,176,198,250,139,251,198,221,225,128,109,113,23,220,39,143,71, + 30,59,189,51,244,61,158,214,146,180,196,103,169,189,221,136,78,129,216,148, + 2,9,8,65,37,224,215,233,13,209,21,235,20,143,33,74,59,53,208,90,152,94,251, + 54,114,171,39,88,230,227,158,211,135,37,182,67,205,161,59,20,138,58,253,15, + 53,48,8,157,9,95,197,9,177,116,21,54,9,125,78,109,182,83,20,16,234,223,116, + 41,155,123,87,77,17,120,153,246,239,124,130,105,219,166,146,242,151,66,198, + 75,72,63,28,246,86,16,244,223,22,36,50,15,247,222,98,6,152,136,154,72,150, + 73,127,2,3,1,0,1,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,76, + 33,54,160,229,219,219,193,150,116,245,252,18,39,235,145,86,12,167,171,52, + 117,166,30,83,5,216,245,177,217,247,95,1,136,94,246,212,108,248,230,111, + 225,202,189,6,129,8,70,128,245,18,204,215,87,82,129,253,227,122,66,182,184, + 189,30,193,169,144,218,216,109,105,110,215,144,60,104,162,178,101,164,218, + 122,60,37,41,143,57,150,52,59,51,112,238,113,239,168,114,69,183,143,154,73, + 61,58,80,247,172,95,251,55,28,186,28,200,206,230,118,243,92,202,189,49,76, + 124,252,76,0,247,112,85,194,69,59,222,163,228,103,49,110,104,109,251,155, + 138,9,37,167,49,189,48,134,52,158,185,129,24,96,153,196,251,90,206,76,239, + 175,119,174,165,133,108,222,125,237,125,187,149,152,83,190,16,202,94,202, + 201,40,218,22,254,63,189,41,174,97,140,203,70,18,196,118,237,175,134,79,78, + 246,2,61,54,77,186,112,32,17,193,192,188,217,252,215,200,7,245,180,179,132, + 183,212,229,155,15,152,206,135,56,81,88,3,123,244,149,110,182,72,109,70,62, + 146,152,146,151,107,126,216,210,9,93,0,0>>], + + {[_Header|Encoded], _} = tls_record_1_3:encode_plain_text(22, PlainText, ConnectionStates), + CipherText = #ssl_tls{type = 23, version = {3,3}, fragment = Encoded}, + + {#ssl_tls{type = 22, version = {3,4}, fragment = DecodedText}, _} = + tls_record_1_3:decode_cipher_text(CipherText, ConnectionStates), + + DecodedText = iolist_to_binary(PlainText), + ct:log("Decoded: ~p ~n", [DecodedText]), + ok. +%%-------------------------------------------------------------------- +'1_RTT_handshake'() -> + [{doc,"Test TLS 1.3 1-RTT Handshake"}]. + +'1_RTT_handshake'(_Config) -> + %% ConnectionStates with NULL cipher + ConnStatesNull = + #{current_write => + #{security_parameters => + #security_parameters{cipher_suite = ?TLS_NULL_WITH_NULL_NULL}, + sequence_number => 0 + } + }, + + %% {client} construct a ClientHello handshake message: + %% + %% ClientHello (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 + %% ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 + %% 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b + %% 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 + %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 + %% 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 + %% 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a + %% af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 + %% 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 + %% 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + %% + %% {client} send handshake record: + %% + %% payload (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 ba + %% 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 02 + %% 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b 00 + %% 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12 + %% 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 00 + %% 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 3d + %% 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af + %% 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 + %% 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02 + %% 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + %% + %% complete record (201 octets): 16 03 01 00 c4 01 00 00 c0 03 03 cb + %% 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 + %% ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 + %% 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + %% 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 + %% 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d + %% e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d + %% 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e + %% 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 + %% 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + ClientHello = + hexstr2bin("01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 + ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 + 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b + 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 + 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 + 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 + 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a + af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 + 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 + 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"), + + ClientHelloRecord = + %% Current implementation always sets + %% legacy_record_version to Ox0303 + hexstr2bin("16 03 03 00 c4 01 00 00 c0 03 03 cb + 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 + ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 + 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 + 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d + e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d + 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e + 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 + 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"), + + {CHEncrypted, _} = + tls_record:encode_handshake(ClientHello, {3,4}, ConnStatesNull), + ClientHelloRecord = iolist_to_binary(CHEncrypted), + + %% {server} extract secret "early": + %% + %% salt: 0 (all zero octets) + %% + %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% + %% secret (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c + %% e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a + HKDFAlgo = sha256, + Salt = binary:copy(<<?BYTE(0)>>, 32), + IKM = binary:copy(<<?BYTE(0)>>, 32), + EarlySecret = + hexstr2bin("33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c + e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a"), + + {early_secret, EarlySecret} = tls_v1:key_schedule(early_secret, HKDFAlgo, {psk, Salt}), + + %% {client} create an ephemeral x25519 key pair: + %% + %% private key (32 octets): 49 af 42 ba 7f 79 94 85 2d 71 3e f2 78 + %% 4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05 + %% + %% public key (32 octets): 99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d + %% ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c + CPublicKey = + hexstr2bin("99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d + ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c"), + + %% {server} create an ephemeral x25519 key pair: + %% + %% private key (32 octets): b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 + %% 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e + %% + %% public key (32 octets): c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 + %% 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f + SPrivateKey = + hexstr2bin("b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 + 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e"), + + SPublicKey = + hexstr2bin("c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 + 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f"), + + %% {server} construct a ServerHello handshake message: + %% + %% ServerHello (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 + %% dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e + %% d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 + %% 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 + %% dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 + ServerHello = + hexstr2bin("02 00 00 56 03 03 a6 af 06 a4 12 18 60 + dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e + d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 + 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 + dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"), + + %% {server} derive secret for handshake "tls13 derived": + %% + %% PRK (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2 + %% 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a + %% + %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 + %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 + %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 + %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% expanded (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba + %% b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba + Hash = + hexstr2bin("e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 + 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55"), + + Hash = crypto:hash(HKDFAlgo, <<>>), + + Info = + hexstr2bin("00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 + 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 + 64 9b 93 4c a4 95 99 1b 78 52 b8 55"), + + Info = tls_v1:create_info(<<"derived">>, Hash, ssl_cipher:hash_size(HKDFAlgo)), + + Expanded = + hexstr2bin("6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba + b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba"), + + Expanded = tls_v1:derive_secret(EarlySecret, <<"derived">>, <<>>, HKDFAlgo), + + %% {server} extract secret "handshake": + %% + %% salt (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba b6 97 + %% 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba + %% + %% IKM (32 octets): 8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d + %% 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d + %% + %% secret (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b + %% 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + + %% salt = Expanded + HandshakeIKM = + hexstr2bin("8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d + 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d"), + + HandshakeSecret = + hexstr2bin("1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b + 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac"), + + HandshakeIKM = crypto:compute_key(ecdh, CPublicKey, SPrivateKey, x25519), + + {handshake_secret, HandshakeSecret} = + tls_v1:key_schedule(handshake_secret, HKDFAlgo, HandshakeIKM, + {early_secret, EarlySecret}), + + %% {server} derive secret "tls13 c hs traffic": + %% + %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 + %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + %% + %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed + %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72 + %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% expanded (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e + %% 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 + + %% PRK = HandshakeSecret + CHSTHash = + hexstr2bin("86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed + d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), + + CHSTInfo = + hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72 + 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), + + CHSTrafficSecret = + hexstr2bin(" b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e + 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21"), + + CHSH = <<ClientHello/binary,ServerHello/binary>>, + CHSTHash = crypto:hash(HKDFAlgo, CHSH), + CHSTInfo = tls_v1:create_info(<<"c hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)), + + CHSTrafficSecret = + tls_v1:client_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH), + + %% {server} derive secret "tls13 s hs traffic": + %% + %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 + %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + %% + %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed + %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72 + %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% expanded (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d + %% 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 + + %% PRK = HandshakeSecret + %% hash = CHSTHash + SHSTInfo = + hexstr2bin("00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72 + 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), + + SHSTrafficSecret = + hexstr2bin("b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d + 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38"), + + SHSTInfo = tls_v1:create_info(<<"s hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)), + + SHSTrafficSecret = + tls_v1:server_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH), + + + %% {server} derive secret for master "tls13 derived": + %% + %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 + %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + %% + %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 + %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 + %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 + %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% expanded (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 + %% 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4 + + %% PRK = HandshakeSecret + %% hash = Hash + %% info = Info + MasterDeriveSecret = + hexstr2bin("43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 + 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4"), + + MasterDeriveSecret = tls_v1:derive_secret(HandshakeSecret, <<"derived">>, <<>>, HKDFAlgo), + + %% {server} extract secret "master": + %% + %% salt (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 90 b5 + %% 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4 + %% + %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% + %% secret (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a + %% 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + + %% salt = MasterDeriveSecret + %% IKM = IKM + MasterSecret = + hexstr2bin("18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a + 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19"), + + {master_secret, MasterSecret} = + tls_v1:key_schedule(master_secret, HKDFAlgo, {handshake_secret, HandshakeSecret}), + + %% {server} send handshake record: + %% + %% payload (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 dc 5e + %% 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e d3 e2 + %% 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 76 11 + %% 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 + %% b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 + %% + %% complete record (95 octets): 16 03 03 00 5a 02 00 00 56 03 03 a6 + %% af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 + %% 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 + %% 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 + %% cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 + + %% payload = ServerHello + ServerHelloRecord = + hexstr2bin("16 03 03 00 5a 02 00 00 56 03 03 a6 + af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 + 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 + 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 + cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"), + + {SHEncrypted, _} = + tls_record:encode_handshake(ServerHello, {3,4}, ConnStatesNull), + ServerHelloRecord = iolist_to_binary(SHEncrypted), + + %% {server} derive write traffic keys for handshake data: + %% + %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 + %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 + %% + %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 + %% + %% key expanded (16 octets): 3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e + %% e4 03 bc + %% + %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 + %% + %% iv expanded (12 octets): 5d 31 3e b2 67 12 76 ee 13 00 0b 30 + + %% PRK = SHSTrafficSecret + WriteKeyInfo = + hexstr2bin("00 10 09 74 6c 73 31 33 20 6b 65 79 00"), + + WriteKey = + hexstr2bin("3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e e4 03 bc"), + + WriteIVInfo = + hexstr2bin("00 0c 08 74 6c 73 31 33 20 69 76 00"), + + WriteIV = + hexstr2bin(" 5d 31 3e b2 67 12 76 ee 13 00 0b 30"), + + Cipher = aes_128_gcm, %% TODO: get from ServerHello + + WriteKeyInfo = tls_v1:create_info(<<"key">>, <<>>, ssl_cipher:key_material(Cipher)), + %% TODO: remove hardcoded IV size + WriteIVInfo = tls_v1:create_info(<<"iv">>, <<>>, 12), + + {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SHSTrafficSecret), + + %% {server} construct an EncryptedExtensions handshake message: + %% + %% EncryptedExtensions (40 octets): 08 00 00 24 00 22 00 0a 00 14 00 + %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c + %% 00 02 40 01 00 00 00 00 + %% + %% {server} construct a Certificate handshake message: + %% + %% Certificate (445 octets): 0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 + %% 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 + %% 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 + %% 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 + %% 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 + %% 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 + %% 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f + %% 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 + %% d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c + %% 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 + %% 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 + %% 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 + %% ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 + %% 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 + %% 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 + %% 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a + %% 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea + %% e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 + %% 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be + %% c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b + %% 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 + %% 96 12 29 ac 91 87 b4 2b 4d e1 00 00 + %% + %% {server} construct a CertificateVerify handshake message: + %% + %% CertificateVerify (136 octets): 0f 00 00 84 08 04 00 80 5a 74 7c + %% 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a + %% b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 + %% 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b + %% be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 + %% 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a + %% 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3 + EncryptedExtensions = + hexstr2bin("08 00 00 24 00 22 00 0a 00 14 00 + 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c + 00 02 40 01 00 00 00 00"), + + Certificate = + hexstr2bin("0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 + 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 + 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 + 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 + 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 + 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 + 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f + 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 + d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c + 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 + 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 + 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 + ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 + 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 + 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 + 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a + 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea + e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 + 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be + c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b + 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 + 96 12 29 ac 91 87 b4 2b 4d e1 00 00"), + + CertificateVerify = + hexstr2bin("0f 00 00 84 08 04 00 80 5a 74 7c + 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a + b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 + 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b + be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 + 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a + 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3"), + + %% {server} calculate finished "tls13 finished": + %% + %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 + %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 + %% + %% hash (0 octets): (empty) + %% + %% info (18 octets): 00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65 + %% 64 00 + %% + %% expanded (32 octets): 00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 + %% c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8 + %% + %% finished (32 octets): 9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 + %% de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18 + + %% PRK = SHSTrafficSecret + FInfo = + hexstr2bin("00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65 + 64 00"), + + FExpanded = + hexstr2bin("00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 + c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8"), + + FinishedVerifyData = + hexstr2bin("9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 + de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18"), + + FInfo = tls_v1:create_info(<<"finished">>, <<>>, ssl_cipher:hash_size(HKDFAlgo)), + + FExpanded = tls_v1:finished_key(SHSTrafficSecret, HKDFAlgo), + + MessageHistory0 = [CertificateVerify, + Certificate, + EncryptedExtensions, + ServerHello, + ClientHello], + + FinishedVerifyData = tls_v1:finished_verify_data(FExpanded, HKDFAlgo, MessageHistory0), + + %% {server} construct a Finished handshake message: + %% + %% Finished (36 octets): 14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb + %% dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 + %% 18 + FinishedHSBin = + hexstr2bin("14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb + dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 + 18"), + + FinishedHS = #finished{verify_data = FinishedVerifyData}, + + FinishedIOList = tls_handshake:encode_handshake(FinishedHS, {3,4}), + FinishedHSBin = iolist_to_binary(FinishedIOList), + + %% {server} derive secret "tls13 c ap traffic": + %% + %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 + %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + %% + %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72 + %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% expanded (32 octets): 9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce + %% 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5 + + %% PRK = MasterSecret + CAPTHash = + hexstr2bin("96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + CAPTInfo = + hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72 + 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + + CAPTrafficSecret = + hexstr2bin("9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce + 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5"), + + CHSF = <<ClientHello/binary, + ServerHello/binary, + EncryptedExtensions/binary, + Certificate/binary, + CertificateVerify/binary, + FinishedHSBin/binary>>, + + CAPTHash = crypto:hash(HKDFAlgo, CHSF), + + CAPTInfo = + tls_v1:create_info(<<"c ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), + + CAPTrafficSecret = + tls_v1:client_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF), + + %% {server} derive secret "tls13 s ap traffic": + %% + %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 + %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + %% + %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72 + %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% expanded (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 + %% 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 + + %% PRK = MasterSecret + %% hash = CAPTHash + SAPTInfo = + hexstr2bin(" 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72 + 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + + SAPTrafficSecret = + hexstr2bin("a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 + 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43"), + + SAPTInfo = + tls_v1:create_info(<<"s ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), + + SAPTrafficSecret = + tls_v1:server_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF), + + %% {server} derive secret "tls13 exp master": + %% + %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 + %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + %% + %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% info (52 octets): 00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 + %% 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 + %% 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% expanded (32 octets): fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 + %% 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50 + + %% PRK = MasterSecret + %% hash = CAPTHash + ExporterInfo = + hexstr2bin("00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 + 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 + 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + + ExporterMasterSecret = + hexstr2bin("fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 + 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50"), + + ExporterInfo = + tls_v1:create_info(<<"exp master">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), + + ExporterMasterSecret = + tls_v1:exporter_master_secret(HKDFAlgo, {master_secret, MasterSecret}, CHSF), + + %% {server} derive write traffic keys for application data: + %% + %% PRK (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32 + %% 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 + %% + %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 + %% + %% key expanded (16 octets): 9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac + %% 92 e3 56 + %% + %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 + %% + %% iv expanded (12 octets): cf 78 2b 88 dd 83 54 9a ad f1 e9 84 + + %% PRK = SAPTrafficsecret + %% key info = WriteKeyInfo + %% iv info = WrtieIVInfo + SWKey = + hexstr2bin("9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac 92 e3 56"), + + SWIV = + hexstr2bin("cf 78 2b 88 dd 83 54 9a ad f1 e9 84"), + + {SWKey, SWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SAPTrafficSecret), + + %% {server} derive read traffic keys for handshake data: + %% + %% PRK (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f + %% 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 + %% + %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 + %% + %% key expanded (16 octets): db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 + %% 25 8d 01 + %% + %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 + %% + %% iv expanded (12 octets): 5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f + + %% PRK = CHSTrafficsecret + %% key info = WriteKeyInfo + %% iv info = WrtieIVInfo + SRKey = + hexstr2bin("db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 25 8d 01"), + + SRIV = + hexstr2bin("5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f"), + + {SRKey, SRIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CHSTrafficSecret). + +%%-------------------------------------------------------------------- +finished_verify_data() -> + [{doc,"Test TLS 1.3 Finished message handling"}]. + +finished_verify_data(_Config) -> + ClientHello = + hexstr2bin("01 00 00 c6 03 03 00 01 02 03 04 05 06 07 08 09 + 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 + 1a 1b 1c 1d 1e 1f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 + e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 + f9 fa fb fc fd fe ff 00 06 13 01 13 02 13 03 01 + 00 00 77 00 00 00 18 00 16 00 00 13 65 78 61 6d + 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e 65 74 00 + 0a 00 08 00 06 00 1d 00 17 00 18 00 0d 00 14 00 + 12 04 03 08 04 04 01 05 03 08 05 05 01 08 06 06 + 01 02 01 00 33 00 26 00 24 00 1d 00 20 35 80 72 + d6 36 58 80 d1 ae ea 32 9a df 91 21 38 38 51 ed + 21 a2 8e 3b 75 e9 65 d0 d2 cd 16 62 54 00 2d 00 + 02 01 01 00 2b 00 03 02 03 04"), + + ServerHello = + hexstr2bin("02 00 00 76 03 03 70 71 72 73 74 75 76 77 78 79 + 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 + 8a 8b 8c 8d 8e 8f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 + e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 + f9 fa fb fc fd fe ff 13 01 00 00 2e 00 33 00 24 + 00 1d 00 20 9f d7 ad 6d cf f4 29 8d d3 f9 6d 5b + 1b 2a f9 10 a0 53 5b 14 88 d7 f8 fa bb 34 9a 98 + 28 80 b6 15 00 2b 00 02 03 04"), + + EncryptedExtensions = + hexstr2bin("08 00 00 02 00 00"), + + Certificate = + hexstr2bin("0b 00 03 2e 00 00 03 2a 00 03 25 30 82 03 21 30 + 82 02 09 a0 03 02 01 02 02 08 15 5a 92 ad c2 04 + 8f 90 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 + 00 30 22 31 0b 30 09 06 03 55 04 06 13 02 55 53 + 31 13 30 11 06 03 55 04 0a 13 0a 45 78 61 6d 70 + 6c 65 20 43 41 30 1e 17 0d 31 38 31 30 30 35 30 + 31 33 38 31 37 5a 17 0d 31 39 31 30 30 35 30 31 + 33 38 31 37 5a 30 2b 31 0b 30 09 06 03 55 04 06 + 13 02 55 53 31 1c 30 1a 06 03 55 04 03 13 13 65 + 78 61 6d 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e + 65 74 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d + 01 01 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 + 01 01 00 c4 80 36 06 ba e7 47 6b 08 94 04 ec a7 + b6 91 04 3f f7 92 bc 19 ee fb 7d 74 d7 a8 0d 00 + 1e 7b 4b 3a 4a e6 0f e8 c0 71 fc 73 e7 02 4c 0d + bc f4 bd d1 1d 39 6b ba 70 46 4a 13 e9 4a f8 3d + f3 e1 09 59 54 7b c9 55 fb 41 2d a3 76 52 11 e1 + f3 dc 77 6c aa 53 37 6e ca 3a ec be c3 aa b7 3b + 31 d5 6c b6 52 9c 80 98 bc c9 e0 28 18 e2 0b f7 + f8 a0 3a fd 17 04 50 9e ce 79 bd 9f 39 f1 ea 69 + ec 47 97 2e 83 0f b5 ca 95 de 95 a1 e6 04 22 d5 + ee be 52 79 54 a1 e7 bf 8a 86 f6 46 6d 0d 9f 16 + 95 1a 4c f7 a0 46 92 59 5c 13 52 f2 54 9e 5a fb + 4e bf d7 7a 37 95 01 44 e4 c0 26 87 4c 65 3e 40 + 7d 7d 23 07 44 01 f4 84 ff d0 8f 7a 1f a0 52 10 + d1 f4 f0 d5 ce 79 70 29 32 e2 ca be 70 1f df ad + 6b 4b b7 11 01 f4 4b ad 66 6a 11 13 0f e2 ee 82 + 9e 4d 02 9d c9 1c dd 67 16 db b9 06 18 86 ed c1 + ba 94 21 02 03 01 00 01 a3 52 30 50 30 0e 06 03 + 55 1d 0f 01 01 ff 04 04 03 02 05 a0 30 1d 06 03 + 55 1d 25 04 16 30 14 06 08 2b 06 01 05 05 07 03 + 02 06 08 2b 06 01 05 05 07 03 01 30 1f 06 03 55 + 1d 23 04 18 30 16 80 14 89 4f de 5b cc 69 e2 52 + cf 3e a3 00 df b1 97 b8 1d e1 c1 46 30 0d 06 09 + 2a 86 48 86 f7 0d 01 01 0b 05 00 03 82 01 01 00 + 59 16 45 a6 9a 2e 37 79 e4 f6 dd 27 1a ba 1c 0b + fd 6c d7 55 99 b5 e7 c3 6e 53 3e ff 36 59 08 43 + 24 c9 e7 a5 04 07 9d 39 e0 d4 29 87 ff e3 eb dd + 09 c1 cf 1d 91 44 55 87 0b 57 1d d1 9b df 1d 24 + f8 bb 9a 11 fe 80 fd 59 2b a0 39 8c de 11 e2 65 + 1e 61 8c e5 98 fa 96 e5 37 2e ef 3d 24 8a fd e1 + 74 63 eb bf ab b8 e4 d1 ab 50 2a 54 ec 00 64 e9 + 2f 78 19 66 0d 3f 27 cf 20 9e 66 7f ce 5a e2 e4 + ac 99 c7 c9 38 18 f8 b2 51 07 22 df ed 97 f3 2e + 3e 93 49 d4 c6 6c 9e a6 39 6d 74 44 62 a0 6b 42 + c6 d5 ba 68 8e ac 3a 01 7b dd fc 8e 2c fc ad 27 + cb 69 d3 cc dc a2 80 41 44 65 d3 ae 34 8c e0 f3 + 4a b2 fb 9c 61 83 71 31 2b 19 10 41 64 1c 23 7f + 11 a5 d6 5c 84 4f 04 04 84 99 38 71 2b 95 9e d6 + 85 bc 5c 5d d6 45 ed 19 90 94 73 40 29 26 dc b4 + 0e 34 69 a1 59 41 e8 e2 cc a8 4b b6 08 46 36 a0 + 00 00"), + + CertificateVerify = + hexstr2bin("0f 00 01 04 08 04 01 00 17 fe b5 33 ca 6d 00 7d + 00 58 25 79 68 42 4b bc 3a a6 90 9e 9d 49 55 75 + 76 a5 20 e0 4a 5e f0 5f 0e 86 d2 4f f4 3f 8e b8 + 61 ee f5 95 22 8d 70 32 aa 36 0f 71 4e 66 74 13 + 92 6e f4 f8 b5 80 3b 69 e3 55 19 e3 b2 3f 43 73 + df ac 67 87 06 6d cb 47 56 b5 45 60 e0 88 6e 9b + 96 2c 4a d2 8d ab 26 ba d1 ab c2 59 16 b0 9a f2 + 86 53 7f 68 4f 80 8a ef ee 73 04 6c b7 df 0a 84 + fb b5 96 7a ca 13 1f 4b 1c f3 89 79 94 03 a3 0c + 02 d2 9c bd ad b7 25 12 db 9c ec 2e 5e 1d 00 e5 + 0c af cf 6f 21 09 1e bc 4f 25 3c 5e ab 01 a6 79 + ba ea be ed b9 c9 61 8f 66 00 6b 82 44 d6 62 2a + aa 56 88 7c cf c6 6a 0f 38 51 df a1 3a 78 cf f7 + 99 1e 03 cb 2c 3a 0e d8 7d 73 67 36 2e b7 80 5b + 00 b2 52 4f f2 98 a4 da 48 7c ac de af 8a 23 36 + c5 63 1b 3e fa 93 5b b4 11 e7 53 ca 13 b0 15 fe + c7 e4 a7 30 f1 36 9f 9e"), + + BaseKey = + hexstr2bin("a2 06 72 65 e7 f0 65 2a 92 3d 5d 72 ab 04 67 c4 + 61 32 ee b9 68 b6 a3 2d 31 1c 80 58 68 54 88 14"), + + VerifyData = + hexstr2bin("ea 6e e1 76 dc cc 4a f1 85 9e 9e 4e 93 f7 97 ea + c9 a7 8c e4 39 30 1e 35 27 5a d4 3f 3c dd bd e3"), + + Messages = [CertificateVerify, + Certificate, + EncryptedExtensions, + ServerHello, + ClientHello], + + FinishedKey = tls_v1:finished_key(BaseKey, sha256), + VerifyData = tls_v1:finished_verify_data(FinishedKey, sha256, Messages). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +hexstr2int(S) -> + B = hexstr2bin(S), + Bits = size(B) * 8, + <<Integer:Bits/integer>> = B, + Integer. + +hexstr2bin(S) when is_binary(S) -> + hexstr2bin(S, <<>>); +hexstr2bin(S) -> + hexstr2bin(list_to_binary(S), <<>>). +%% +hexstr2bin(<<>>, Acc) -> + Acc; +hexstr2bin(<<C,T/binary>>, Acc) when C =:= 32; %% SPACE + C =:= 10; %% LF + C =:= 13 -> %% CR + hexstr2bin(T, Acc); +hexstr2bin(<<X,Y,T/binary>>, Acc) -> + I = hex2int(X) * 16 + hex2int(Y), + hexstr2bin(T, <<Acc/binary,I>>). + +hex2int(C) when $0 =< C, C =< $9 -> + C - $0; +hex2int(C) when $A =< C, C =< $F -> + C - $A + 10; +hex2int(C) when $a =< C, C =< $f -> + C - $a + 10. diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl new file mode 100644 index 0000000000..f0b224d4e5 --- /dev/null +++ b/lib/ssl/test/tls_1_3_version_SUITE.erl @@ -0,0 +1,153 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(tls_1_3_version_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, 'tlsv1.3'} + ]. + +groups() -> + [ + {'tlsv1.3', [], cert_groups()}, + {rsa, [], tests()}, + {ecdsa, [], tests()} + ]. + +cert_groups() -> + [{group, rsa}, + {group, ecdsa}]. + +tests() -> + [tls13_client_tls12_server, + tls13_client_with_ext_tls12_server, + tls12_client_tls13_server]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + [{client_type, erlang}, {server_type, erlang} | + Config] + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(rsa, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; +init_per_group(ecdsa, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso + (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso + ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +tls13_client_tls12_server() -> + [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}]. + +tls13_client_tls12_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +tls13_client_with_ext_tls12_server() -> + [{doc,"Test basic connection between TLS 1.2 server and TLS 1.3 client when " + "client has TLS 1.3 specsific extensions"}]. + +tls13_client_with_ext_tls12_server(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + ServerOpts = [{versions, ['tlsv1.2']}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {signature_algs_cert, [ecdsa_secp384r1_sha384, + ecdsa_secp256r1_sha256, + rsa_pss_rsae_sha256, + rsa_pkcs1_sha256, + {sha256,rsa},{sha256,dsa}]}|ClientOpts0], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +tls12_client_tls13_server() -> + [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}]. + +tls12_client_tls13_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl new file mode 100644 index 0000000000..5a74ec1892 --- /dev/null +++ b/lib/ssl/test/tls_api_SUITE.erl @@ -0,0 +1,880 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(tls_api_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/ssl_record.hrl"). +-include_lib("ssl/src/ssl_internal.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). +-include_lib("ssl/src/tls_handshake.hrl"). + +-define(SLEEP, 500). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'} + ]. + +groups() -> + [ + {'tlsv1.3', [], api_tests() -- [sockname]}, + {'tlsv1.2', [], api_tests()}, + {'tlsv1.1', [], api_tests()}, + {'tlsv1', [], api_tests()}, + {'sslv3', [], api_tests() ++ [ssl3_cipher_suite_limitation]} + ]. + +api_tests() -> + [ + tls_upgrade, + tls_upgrade_with_timeout, + tls_downgrade, + tls_shutdown, + tls_shutdown_write, + tls_shutdown_both, + tls_shutdown_error, + tls_client_closes_socket, + tls_closed_in_active_once, + tls_tcp_msg, + tls_tcp_msg_big, + tls_dont_crash_on_handshake_garbage, + tls_tcp_error_propagation_in_active_mode, + peername, + sockname, + tls_server_handshake_timeout, + transport_close, + emulated_options, + accept_pool, + reuseaddr + ]. + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +tls_upgrade() -> + [{doc,"Test that you can upgrade an tcp connection to an ssl connection"}]. + +tls_upgrade(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + upgrade_result, []}}, + {tcp_options, + [{active, false} | TcpOpts]}, + {ssl_options, [{verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, + {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, upgrade_result, []}}, + {tcp_options, [binary]}, + {ssl_options, [{verify, verify_peer}, + {server_name_indication, Hostname} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +tls_upgrade_with_timeout() -> + [{doc,"Test ssl_accept/3"}]. + +tls_upgrade_with_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {?MODULE, + upgrade_result, []}}, + {tcp_options, + [{active, false} | TcpOpts]}, + {ssl_options, [{verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, + {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, upgrade_result, []}}, + {tcp_options, TcpOpts}, + {ssl_options, [{verify, verify_peer}, + {server_name_indication, Hostname} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +tls_downgrade() -> + [{doc,"Test that you can downgarde an ssl connection to an tcp connection"}]. +tls_downgrade(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_downgrade_result, [self()]}}, + {options, [{active, false}, {verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_downgrade_result, [self()]}}, + {options, [{active, false}, {verify, verify_peer} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ready, Client, ready), + + Server ! go, + Client ! go, + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +tls_shutdown() -> + [{doc,"Test API function ssl:shutdown/2"}]. +tls_shutdown(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_result, [server]}}, + {options, [{exit_on_close, false}, + {active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, + {?MODULE, tls_shutdown_result, [client]}}, + {options, + [{exit_on_close, false}, + {active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +tls_shutdown_write() -> + [{doc,"Test API function ssl:shutdown/2 with option write."}]. +tls_shutdown_write(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_write_result, [server]}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_write_result, [client]}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, {error, closed}). + +%%-------------------------------------------------------------------- +tls_shutdown_both() -> + [{doc,"Test API function ssl:shutdown/2 with option both."}]. +tls_shutdown_both(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_both_result, [server]}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_both_result, [client]}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, {error, closed}). + +%%-------------------------------------------------------------------- +tls_shutdown_error() -> + [{doc,"Test ssl:shutdown/2 error handling"}]. +tls_shutdown_error(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Port = ssl_test_lib:inet_port(node()), + {ok, Listen} = ssl:listen(Port, ServerOpts), + {error, enotconn} = ssl:shutdown(Listen, read_write), + ok = ssl:close(Listen), + {error, closed} = ssl:shutdown(Listen, read_write). +%%-------------------------------------------------------------------- +tls_client_closes_socket() -> + [{doc,"Test what happens when client closes socket before handshake is compleated"}]. + +tls_client_closes_socket(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {tcp_options, TcpOpts}, + {ssl_options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Connect = fun() -> + {ok, _Socket} = rpc:call(ClientNode, gen_tcp, connect, + [Hostname, Port, [binary]]), + %% Make sure that ssl_accept is called before + %% client process ends and closes socket. + ct:sleep(?SLEEP) + end, + + _Client = spawn_link(Connect), + + ssl_test_lib:check_result(Server, {error,closed}). + +%%-------------------------------------------------------------------- +tls_closed_in_active_once() -> + [{doc, "Test that ssl_closed is delivered in active once with non-empty buffer, check ERL-420."}]. + +tls_closed_in_active_once(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + Port = ssl_test_lib:inet_port(node()), + Server = fun() -> + {ok, Listen} = gen_tcp:listen(Port, TcpOpts), + {ok, TcpServerSocket} = gen_tcp:accept(Listen), + {ok, ServerSocket} = ssl:handshake(TcpServerSocket, ServerOpts), + lists:foreach( + fun(_) -> + ssl:send(ServerSocket, "some random message\r\n") + end, lists:seq(1, 20)), + %% Close TCP instead of SSL socket to trigger the bug: + gen_tcp:close(TcpServerSocket), + gen_tcp:close(Listen) + end, + spawn_link(Server), + {ok, Socket} = ssl:connect(Hostname, Port, [{active, false} | ClientOpts]), + Result = tls_closed_in_active_once_loop(Socket), + ssl:close(Socket), + case Result of + ok -> ok; + _ -> ct:fail(Result) + end. +%%-------------------------------------------------------------------- +tls_tcp_msg() -> + [{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}]. + +tls_tcp_msg(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}, {active, false}], + + Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {?MODULE, dummy, []}}, + {tcp_options, TcpOpts}, + {ssl_options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), + ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), + gen_tcp:send(Socket, "<SOME GARBLED NON SSL MESSAGE>"), + + receive + {tcp_closed, Socket} -> + receive + {Server, {error, Error}} -> + ct:log("Error ~p", [Error]) + end + end. +%%-------------------------------------------------------------------- +tls_tcp_msg_big() -> + [{doc,"Test what happens when a tcp tries to connect, i,e. a bad big (ssl) packet is sent first"}]. + +tls_tcp_msg_big(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Rand = crypto:strong_rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), + Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {?MODULE, dummy, []}}, + {tcp_options, TcpOpts}, + {ssl_options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), + ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), + + gen_tcp:send(Socket, <<?BYTE(0), + ?BYTE(3), ?BYTE(1), ?UINT16(?MAX_CIPHER_TEXT_LENGTH), Rand/binary>>), + + receive + {tcp_closed, Socket} -> + receive + {Server, {error, timeout}} -> + ct:fail("hangs"); + {Server, {error, Error}} -> + ct:log("Error ~p", [Error]); + {'EXIT', Server, _} -> + ok + end + end. + +%%-------------------------------------------------------------------- +tls_dont_crash_on_handshake_garbage() -> + [{doc, "Ensure SSL server worker thows an alert on garbage during handshake " + "instead of crashing and exposing state to user code"}]. + +tls_dont_crash_on_handshake_garbage(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + unlink(Server), monitor(process, Server), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), + + % Send hello and garbage record + ok = gen_tcp:send(Socket, + [<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello + 16#deadbeef:256, % 32 'random' bytes = 256 bits + 0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values + + <<22, 3,3, 5:16, 92,64,37,228,209>> % garbage + ]), + % Send unexpected change_cipher_spec + ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>), + + % Ensure we receive an alert, not sudden disconnect + {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000). + +%%-------------------------------------------------------------------- +tls_tcp_error_propagation_in_active_mode() -> + [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}]. +tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, #sslsocket{pid=[Pid|_]} = SslSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, receive_msg, []}}, + {options, ClientOpts}]), + + {status, _, _, StatusInfo} = sys:get_status(Pid), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + StaticEnv = element(2, State), + Socket = element(11, StaticEnv), + %% Fake tcp error + Pid ! {tcp_error, Socket, etimedout}, + + ssl_test_lib:check_result(Client, {ssl_closed, SslSocket}). + +%%-------------------------------------------------------------------- +peername() -> + [{doc,"Test API function peername/1"}]. + +peername(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, peername_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, peername_result, []}}, + {options, [{port, 0} | ClientOpts]}]), + + ClientPort = ssl_test_lib:inet_port(Client), + ServerIp = ssl_test_lib:node_to_hostip(ServerNode, server), + ClientIp = ssl_test_lib:node_to_hostip(ClientNode, client), + ServerMsg = {ok, {ClientIp, ClientPort}}, + ClientMsg = {ok, {ServerIp, Port}}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +sockname() -> + [{doc,"Test API function sockname/1"}]. +sockname(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, sockname_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, sockname_result, []}}, + {options, [{port, 0} | ClientOpts]}]), + + ClientPort = ssl_test_lib:inet_port(Client), + ServerIp = ssl_test_lib:node_to_hostip(ServerNode, server), + ClientIp = ssl_test_lib:node_to_hostip(ClientNode, client), + ServerMsg = {ok, {ServerIp, Port}}, + ClientMsg = {ok, {ClientIp, ClientPort}}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +tls_server_handshake_timeout() -> + [{doc,"Test server handshake timeout"}]. + +tls_server_handshake_timeout(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {ssl_test_lib, + no_result_msg, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {ok, CSocket} = gen_tcp:connect(Hostname, Port, [binary, {active, true}]), + + receive + {tcp_closed, CSocket} -> + ssl_test_lib:check_result(Server, {error, timeout}), + receive + {'EXIT', Server, _} -> + %% Make sure supervisor had time to react on process exit + %% Could we come up with a better solution to this? + ct:sleep(500), + [] = supervisor:which_children(tls_connection_sup) + end + end. +transport_close() -> + [{doc, "Test what happens if socket is closed on TCP level after a while of normal operation"}]. +transport_close(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + {ok, TcpS} = rpc:call(ClientNode, gen_tcp, connect, + [Hostname,Port,[binary, {active, false}]]), + {ok, SslS} = rpc:call(ClientNode, ssl, connect, + [TcpS,[{active, false}|ClientOpts]]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), self(), Server]), + ok = ssl:send(SslS, "Hello world"), + {ok,<<"Hello world">>} = ssl:recv(SslS, 11), + gen_tcp:close(TcpS), + {error, _} = ssl:send(SslS, "Hello world"). + +%%-------------------------------------------------------------------- +ssl3_cipher_suite_limitation() -> + [{doc,"Test a SSLv3 client cannot negotiate a TLSv* cipher suite."}]. +ssl3_cipher_suite_limitation(Config) when is_list(Config) -> + + {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), + ok = gen_tcp:send(Socket, + <<22, 3,0, 49:16, % handshake, SSL 3.0, length + 1, 45:24, % client_hello, length + 3,0, % SSL 3.0 + 16#deadbeef:256, % 32 'random' bytes = 256 bits + 0, % no session ID + %% three cipher suites -- null, one with sha256 hash and one with sha hash + 6:16, 0,255, 0,61, 0,57, + 1, 0 % no compression + >>), + {ok, <<22, RecMajor:8, RecMinor:8, _RecLen:16, 2, HelloLen:24>>} = gen_tcp:recv(Socket, 9, 10000), + {ok, <<HelloBin:HelloLen/binary>>} = gen_tcp:recv(Socket, HelloLen, 5000), + ServerHello = tls_handshake:decode_handshake({RecMajor, RecMinor}, 2, HelloBin), + case ServerHello of + #server_hello{server_version = {3,0}, cipher_suite = <<0,57>>} -> + ok; + _ -> + ct:fail({unexpected_server_hello, ServerHello}) + end. +%%-------------------------------------------------------------------- +emulated_options() -> + [{doc,"Test API function getopts/2 and setopts/2"}]. + +emulated_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Values = [{mode, list}, {packet, 0}, {header, 0}, + {active, true}], + %% Shall be the reverse order of Values! + Options = [active, header, packet, mode], + + NewValues = [{mode, binary}, {active, once}], + %% Shall be the reverse order of NewValues! + NewOptions = [active, mode], + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + + {ok, Listen} = ssl:listen(0, ServerOpts), + {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), + ok = ssl:setopts(Listen, [{mode, binary}]), + {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), + {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), + ssl:close(Listen). +accept_pool() -> + [{doc,"Test having an accept pool."}]. +accept_pool(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server0 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {accepters, 3}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server0), + [Server1, Server2] = ssl_test_lib:accepters(2), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + Client2 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + ssl_test_lib:check_ok([Server0, Server1, Server2, Client0, Client1, Client2]), + + ssl_test_lib:close(Server0), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Server2), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + ssl_test_lib:close(Client2). + +%%-------------------------------------------------------------------- +reuseaddr() -> + [{doc,"Test reuseaddr option"}]. + +reuseaddr(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{active, false}, {reuseaddr, true}| ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{active, false} | ClientOpts]}]), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {reuseaddr, true} | ServerOpts]}]), + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server1, ok, Client1, ok), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +upgrade_result(Socket) -> + ssl:setopts(Socket, [{active, true}]), + ok = ssl:send(Socket, "Hello world"), + %% Make sure binary is inherited from tcp socket and that we do + %% not get the list default! + receive + {ssl, _, <<"H">>} -> + receive + {ssl, _, <<"ello world">>} -> + ok + end; + {ssl, _, <<"Hello world">>} -> + ok + end. +tls_downgrade_result(Socket, Pid) -> + ok = ssl_test_lib:send_recv_result(Socket), + Pid ! {self(), ready}, + receive + go -> + ok + end, + case ssl:close(Socket, {self(), 10000}) of + {ok, TCPSocket} -> + inet:setopts(TCPSocket, [{active, true}]), + gen_tcp:send(TCPSocket, "Downgraded"), + receive + {tcp, TCPSocket, <<"Downgraded">>} -> + ok; + {tcp_closed, TCPSocket} -> + ct:fail("Peer timed out, downgrade aborted"), + ok; + Other -> + {error, Other} + end; + {error, timeout} -> + ct:fail("Timed out, downgrade aborted"), + ok; + Fail -> + {error, Fail} + end. + +tls_shutdown_result(Socket, server) -> + ssl:send(Socket, "Hej"), + ok = ssl:shutdown(Socket, write), + {ok, "Hej hopp"} = ssl:recv(Socket, 8), + ok; + +tls_shutdown_result(Socket, client) -> + ssl:send(Socket, "Hej hopp"), + ok = ssl:shutdown(Socket, write), + {ok, "Hej"} = ssl:recv(Socket, 3), + ok. + +tls_shutdown_write_result(Socket, server) -> + ct:sleep(?SLEEP), + ssl:shutdown(Socket, write); +tls_shutdown_write_result(Socket, client) -> + ssl:recv(Socket, 0). + +tls_shutdown_both_result(Socket, server) -> + ct:sleep(?SLEEP), + ssl:shutdown(Socket, read_write); +tls_shutdown_both_result(Socket, client) -> + ssl:recv(Socket, 0). + +tls_closed_in_active_once_loop(Socket) -> + case ssl:setopts(Socket, [{active, once}]) of + ok -> + receive + {ssl, Socket, _} -> + tls_closed_in_active_once_loop(Socket); + {ssl_closed, Socket} -> + ok + after 5000 -> + no_ssl_closed_received + end; + {error, closed} -> + ok + end. + +drop_handshakes(Socket, Timeout) -> + {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout), + {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout), + case RecType of + 22 -> drop_handshakes(Socket, Timeout); + _ -> {ok, <<Header/binary, Frag/binary>>} + end. + +receive_msg(_) -> + receive + Msg -> + Msg + end. + +sockname_result(S) -> + ssl:sockname(S). + +peername_result(S) -> + ssl:peername(S). + +tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> + %% Test get/set emulated opts + {ok, DefaultValues} = ssl:getopts(Socket, Options), + ssl:setopts(Socket, NewValues), + {ok, NewValues} = ssl:getopts(Socket, NewOptions), + %% Test get/set inet opts + {ok,[{nodelay,false}]} = ssl:getopts(Socket, [nodelay]), + ssl:setopts(Socket, [{nodelay, true}]), + {ok,[{nodelay, true}]} = ssl:getopts(Socket, [nodelay]), + {ok, All} = ssl:getopts(Socket, []), + ct:log("All opts ~p~n", [All]), + ok. + diff --git a/lib/ssl/test/x509_test.erl b/lib/ssl/test/x509_test.erl index fea01efdaf..faf223ae35 100644 --- a/lib/ssl/test/x509_test.erl +++ b/lib/ssl/test/x509_test.erl @@ -22,7 +22,7 @@ -module(x509_test). - -include_lib("public_key/include/public_key.hrl"). +-include_lib("public_key/include/public_key.hrl"). -export([extensions/1, gen_pem_config_files/3]). |