From 6ec62f7ca98bcc674b806b39d73ded6f0b9a772d Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Thu, 25 Mar 2010 08:24:48 +0000 Subject: OTP-8517 Renegotiation New ssl now properly handles ssl renegotiation, and initiates a renegotiation if ssl/ltls-sequence numbers comes close to the max value. --- lib/ssl/src/ssl.erl | 19 +- lib/ssl/src/ssl_connection.erl | 314 ++++++++++++++++++++++------------ lib/ssl/src/ssl_handshake.erl | 13 +- lib/ssl/src/ssl_internal.hrl | 11 +- lib/ssl/src/ssl_record.erl | 64 ++++--- lib/ssl/src/ssl_record.hrl | 17 +- lib/ssl/test/ssl_basic_SUITE.erl | 276 +++++++++++++++++++++++++----- lib/ssl/test/ssl_test_lib.erl | 57 +++++- lib/ssl/test/ssl_to_openssl_SUITE.erl | 268 +++++++++++++++++++++++++++-- 9 files changed, 833 insertions(+), 206 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index de74c91505..cb678e3f53 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -29,13 +29,15 @@ connect/3, connect/2, connect/4, connection_info/1, controlling_process/2, listen/2, pid/1, peername/1, recv/2, recv/3, send/2, getopts/2, setopts/2, seed/1, sockname/1, peercert/1, - peercert/2, version/0, versions/0, session_info/1, format_error/1]). + peercert/2, version/0, versions/0, session_info/1, format_error/1, + renegotiate/1]). %% Should be deprecated as soon as old ssl is removed %%-deprecated({pid, 1, next_major_release}). -include("ssl_int.hrl"). -include("ssl_internal.hrl"). +-include("ssl_record.hrl"). -record(config, {ssl, %% SSL parameters inet_user, %% User set inet options @@ -436,6 +438,10 @@ versions() -> AvailableVsns = ?DEFAULT_SUPPORTED_VERSIONS, [{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}]. + +renegotiate(#sslsocket{pid = Pid, fd = new_ssl}) -> + ssl_connection:renegotiation(Pid). + %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- @@ -547,6 +553,7 @@ handle_options(Opts0, Role) -> %% Server side option reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun), reuse_sessions = handle_option(reuse_sessions, Opts, true), + renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT), debug = handle_option(debug, Opts, []) }, @@ -555,7 +562,7 @@ handle_options(Opts0, Role) -> depth, certfile, keyfile, key, password, cacertfile, ciphers, debug, reuse_session, reuse_sessions, ssl_imp, - cd_info], + cd_info, renegotiate_at], SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) @@ -617,6 +624,9 @@ validate_option(reuse_session, Value) when is_function(Value) -> validate_option(reuse_sessions, Value) when Value == true; Value == false -> Value; +validate_option(renegotiate_at, Value) when is_integer(Value) -> + min(Value, ?DEFAULT_RENEGOTIATE_AT); + validate_option(debug, Value) when is_list(Value); Value == true -> Value; validate_option(Opt, Value) -> @@ -832,6 +842,11 @@ version() -> Vsns end, {ok, {SSLVsn, CompVsn, LibVsn}}. + +min(N,M) when N < M -> + N; +min(_, M) -> + M. %% Only used to remove exit messages from old ssl %% First is a nonsense clause to provide some diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 4847fd278d..3ede21d377 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -41,14 +41,13 @@ %% Internal application API -export([send/2, send/3, recv/3, connect/7, accept/6, close/1, shutdown/2, new_user/2, get_opts/2, set_opts/2, info/1, session_info/1, - peer_certificate/1, - sockname/1, peername/1]). + peer_certificate/1, sockname/1, peername/1, renegotiation/1]). %% Called by ssl_connection_sup -export([start_link/7]). %% gen_fsm callbacks --export([init/1, hello/2, certify/2, cipher/2, connection/2, connection/3, abbreviated/2, +-export([init/1, hello/2, certify/2, cipher/2, connection/2, abbreviated/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). @@ -86,7 +85,10 @@ bytes_to_read, % integer(), # bytes to read in passive mode user_data_buffer, % binary() %% tls_buffer, % Keeps a lookahead one packet if available - log_alert % boolan() + log_alert, % boolean() + renegotiation, % boolean() + recv_during_renegotiation, %boolean() + send_queue % queue() }). %%==================================================================== @@ -99,15 +101,15 @@ %% Description: %%-------------------------------------------------------------------- send(Pid, Data) -> - sync_send_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, infinity). + sync_send_all_state_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, infinity). send(Pid, Data, Timeout) -> - sync_send_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, Timeout). + sync_send_all_state_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, Timeout). %%-------------------------------------------------------------------- %% Function: %% %% Description: %%-------------------------------------------------------------------- -recv(Pid, Length, Timeout) -> % TODO: Prio with renegotiate? +recv(Pid, Length, Timeout) -> sync_send_all_state_event(Pid, {recv, Length}, Timeout). %%-------------------------------------------------------------------- %% Function: @@ -209,6 +211,14 @@ session_info(ConnectionPid) -> peer_certificate(ConnectionPid) -> sync_send_all_state_event(ConnectionPid, peer_certificate). +%%-------------------------------------------------------------------- +%% Function: +%% +%% Description: +%%-------------------------------------------------------------------- +renegotiation(ConnectionPid) -> + send_all_state_event(ConnectionPid, renegotiate). + %%==================================================================== %% ssl_connection_sup API %%==================================================================== @@ -289,7 +299,7 @@ hello(socket_control, #state{host = Host, port = Port, role = client, hello(socket_control, #state{role = server} = State) -> {next_state, hello, next_record(State)}; -hello(hello, #state{role = client} = State) -> +hello(#hello_request{}, #state{role = client} = State) -> {next_state, hello, State}; hello(#server_hello{cipher_suite = CipherSuite, @@ -358,21 +368,20 @@ hello(Hello = #client_hello{client_version = ClientVersion}, abbreviated(socket_control, #state{role = server} = State) -> {next_state, abbreviated, State}; -abbreviated(hello, State) -> +abbreviated(#hello_request{}, State) -> {next_state, certify, State}; abbreviated(Finished = #finished{}, #state{role = server, negotiated_version = Version, tls_handshake_hashes = Hashes, - session = #session{master_secret = MasterSecret}, - from = From} = State) -> + session = #session{master_secret = MasterSecret}} = State) -> case ssl_handshake:verify_connection(Version, Finished, client, MasterSecret, Hashes) of verified -> - gen_fsm:reply(From, connected), - {next_state, connection, next_record_if_active(State)}; - #alert{} = Alert -> + ack_connection(State), + next_state_connection(State); + #alert{} = Alert -> handle_own_alert(Alert, Version, abbreviated, State), {stop, normal, State} end; @@ -380,17 +389,15 @@ abbreviated(Finished = #finished{}, abbreviated(Finished = #finished{}, #state{role = client, tls_handshake_hashes = Hashes0, session = #session{master_secret = MasterSecret}, - from = From, negotiated_version = Version} = State) -> case ssl_handshake:verify_connection(Version, Finished, server, MasterSecret, Hashes0) of verified -> {ConnectionStates, Hashes} = finalize_client_handshake(State), - gen_fsm:reply(From, connected), - {next_state, connection, - next_record_if_active(State#state{tls_handshake_hashes = Hashes, - connection_states = - ConnectionStates})}; + ack_connection(State), + next_state_connection(State#state{tls_handshake_hashes = Hashes, + connection_states = + ConnectionStates}); #alert{} = Alert -> handle_own_alert(Alert, Version, abbreviated, State), {stop, normal, State} @@ -398,7 +405,7 @@ abbreviated(Finished = #finished{}, certify(socket_control, #state{role = server} = State) -> {next_state, certify, State}; -certify(hello, State) -> +certify(#hello_request{}, State) -> {next_state, certify, State}; certify(#certificate{asn1_certificates = []}, @@ -427,7 +434,7 @@ certify(#certificate{} = Cert, Opts#ssl_options.verify_fun) of {PeerCert, PublicKeyInfo} -> State = State0#state{session = - Session#session{peer_certificate = PeerCert}, + Session#session{peer_certificate = PeerCert}, public_key_info = PublicKeyInfo, client_certificate_requested = false }, @@ -521,7 +528,7 @@ certify(#client_key_exchange{exchange_keys cipher(socket_control, #state{role = server} = State) -> {next_state, cipher, State}; -cipher(hello, State) -> +cipher(#hello_request{}, State) -> {next_state, cipher, State}; cipher(#certificate_verify{signature = Signature}, @@ -543,8 +550,7 @@ cipher(#certificate_verify{signature = Signature}, end; cipher(#finished{} = Finished, - State = #state{from = From, - negotiated_version = Version, + State = #state{negotiated_version = Version, host = Host, port = Port, role = Role, @@ -556,12 +562,11 @@ cipher(#finished{} = Finished, opposite_role(Role), MasterSecret, Hashes) of verified -> - gen_fsm:reply(From, connected), + ack_connection(State), Session = register_session(Role, Host, Port, Session0), case Role of client -> - {next_state, connection, - next_record_if_active(State#state{session = Session})}; + next_state_connection(State#state{session = Session}); server -> {NewConnectionStates, NewHashes} = finalize_server_handshake(State#state{ @@ -570,7 +575,7 @@ cipher(#finished{} = Finished, State#state{connection_states = NewConnectionStates, session = Session, tls_handshake_hashes = NewHashes}, - {next_state, connection, next_record_if_active(NewState)} + next_state_connection(NewState) end; #alert{} = Alert -> handle_own_alert(Alert, Version, cipher, State), @@ -579,7 +584,7 @@ cipher(#finished{} = Finished, connection(socket_control, #state{role = server} = State) -> {next_state, connection, State}; -connection(hello, State = #state{host = Host, port = Port, +connection(#hello_request{}, State = #state{host = Host, port = Port, socket = Socket, ssl_options = SslOpts, negotiated_version = Version, @@ -592,42 +597,12 @@ connection(hello, State = #state{host = Host, port = Port, {BinMsg, ConnectionStates1, Hashes1} = encode_handshake(Hello, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), - {next_state, hello, State#state{connection_states = ConnectionStates1, - tls_handshake_hashes = Hashes1}}. - -%%-------------------------------------------------------------------- -%% Function: -%% state_name(Event, From, State) -> {next_state, NextStateName, NextState} | -%% {next_state, NextStateName, -%% NextState, Timeout} | -%% {reply, Reply, NextStateName, NextState}| -%% {reply, Reply, NextStateName, -%% NextState, Timeout} | -%% {stop, Reason, NewState}| -%% {stop, Reason, Reply, NewState} -%% Description: There should be one instance of this function for each -%% possible state name. Whenever a gen_fsm receives an event sent using -%% gen_fsm:sync_send_event/2,3, the instance of this function with the same -%% name as the current state name StateName is called to handle the event. -%%-------------------------------------------------------------------- -connection({application_data, Data0}, _From, - State = #state{socket = Socket, - negotiated_version = Version, - transport_cb = Transport, - connection_states = ConnectionStates0}) -> - %% We should look into having a worker process to do this to - %% parallize send and receive decoding and not block the receiver - %% if sending is overloading the socket. - try - Data = encode_packet(Data0, State#state.socket_options), - {Msgs, ConnectionStates1} = encode_data(Data, Version, ConnectionStates0), - Result = Transport:send(Socket, Msgs), - {reply, Result, - connection, State#state{connection_states = ConnectionStates1}} - - catch throw:Error -> - {reply, Error, connection, State} - end. + {next_state, hello, next_record(State#state{connection_states = + ConnectionStates1, + tls_handshake_hashes = Hashes1, + renegotiation = true})}; +connection(#client_hello{} = Hello, #state{role = server} = State) -> + hello(Hello, State). %%-------------------------------------------------------------------- %% Function: @@ -642,22 +617,36 @@ connection({application_data, Data0}, _From, %%-------------------------------------------------------------------- handle_event(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, StateName, - State = #state{key_algorithm = KeyAlg, - tls_handshake_buffer = Buf0, - negotiated_version = Version}) -> + State0 = #state{key_algorithm = KeyAlg, + tls_handshake_buffer = Buf0, + negotiated_version = Version}) -> Handle = - fun({Packet, Raw}, {next_state, SName, AS=#state{tls_handshake_hashes=Hs0}}) -> + fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> + %% This message should not be included in handshake + %% message hashes. Starts new handshake (renegotiation) + Hs0 = ssl_handshake:init_hashes(), + ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs0}); + ({#hello_request{} = Packet, _}, {next_state, SName, State}) -> + %% This message should not be included in handshake + %% message hashes. If allready in negotiation it will be ignored! + ?MODULE:SName(Packet, State); + ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) -> + Hs0 = ssl_handshake:init_hashes(), + Hs1 = ssl_handshake:update_hashes(Hs0, Raw), + ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1, + renegotiation = true}); + ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_hashes=Hs0}}) -> Hs1 = ssl_handshake:update_hashes(Hs0, Raw), - ?MODULE:SName(Packet, AS#state{tls_handshake_hashes=Hs1}); + ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1}); (_, StopState) -> StopState end, try {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version), - Start = {next_state, StateName, State#state{tls_handshake_buffer = Buf}}, + Start = {next_state, StateName, State0#state{tls_handshake_buffer = Buf}}, lists:foldl(Handle, Start, Packets) catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State), - {stop, normal, State} + handle_own_alert(Alert, Version, StateName, State0), + {stop, normal, State0} end; handle_event(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, @@ -717,8 +706,13 @@ handle_event(#alert{level = ?WARNING} = Alert, StateName, log_alert(Log, StateName, Alert), %%TODO: Could be user_canceled or no_negotiation should the latter be %% treated as fatal?! - {next_state, StateName, next_record(State)}. + {next_state, StateName, next_record(State)}; +handle_event(renegotiate, connection, State) -> + renegotiate(State); +handle_event(renegotiate, StateName, State) -> + %% Already in renegotiate ignore + {next_state, StateName, State}. %%-------------------------------------------------------------------- %% Function: %% handle_sync_event(Event, From, StateName, @@ -734,6 +728,42 @@ handle_event(#alert{level = ?WARNING} = Alert, StateName, %% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle %% the event. %%-------------------------------------------------------------------- +handle_sync_event({application_data, Data0}, From, connection, + #state{socket = Socket, + negotiated_version = Version, + transport_cb = Transport, + connection_states = ConnectionStates0, + send_queue = SendQueue, + socket_options = SockOpts, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} + = State) -> + %% We should look into having a worker process to do this to + %% parallize send and receive decoding and not block the receiver + %% if sending is overloading the socket. + try + Data = encode_packet(Data0, SockOpts), + case encode_data(Data, Version, ConnectionStates0, RenegotiateAt) of + {Msgs, [], ConnectionStates} -> + Result = Transport:send(Socket, Msgs), + {reply, Result, + connection, State#state{connection_states = ConnectionStates}}; + {Msgs, RestData, ConnectionStates} -> + if + Msgs =/= [] -> + Transport:send(Socket, Msgs); + true -> + ok + end, + renegotiate(State#state{connection_states = ConnectionStates, + send_queue = queue:in_r({From, RestData}, SendQueue)}) + end + catch throw:Error -> + {reply, Error, connection, State} + end; +handle_sync_event({application_data, Data}, From, StateName, + #state{send_queue = Queue} = State) -> + %% In renegotiation priorities handshake, send data when handshake is finished + {next_state, StateName, State#state{send_queue = queue:in({From, Data}, Queue)}}; handle_sync_event(started, From, StateName, State) -> {next_state, StateName, State#state{from = From}}; @@ -750,23 +780,14 @@ handle_sync_event({shutdown, How}, From, StateName, {stop, normal, Error, State#state{from = From}} end; -%% TODO: men vad gör next_record om det är t.ex. renegotiate? kanske -%% inte bra... tål att tänkas på! -handle_sync_event({recv, N}, From, StateName, - State0 = #state{user_data_buffer = Buffer}) -> - State1 = State0#state{bytes_to_read = N, from = From}, - case Buffer of - <<>> -> - State = next_record(State1), - {next_state, StateName, State}; - _ -> - case application_data(<<>>, State1) of - Stop = {stop, _, _} -> - Stop; - State -> - {next_state, StateName, State} - end - end; +handle_sync_event({recv, N}, From, connection = StateName, State0) -> + passive_receive(State0#state{bytes_to_read = N, from = From}, StateName); + +%% Doing renegotiate wait with handling request until renegotiate is +%% finished. Will be handled by next_state_connection/1. +handle_sync_event({recv, N}, From, StateName, State) -> + {next_state, StateName, State#state{bytes_to_read = N, from = From, + recv_during_renegotiation = true}}; handle_sync_event({new_user, User}, _From, StateName, State =#state{user_application = {OldMon, _}}) -> @@ -834,7 +855,6 @@ handle_sync_event(peer_certificate, _, StateName, = State) -> {reply, {ok, Cert}, StateName, State}. - %%-------------------------------------------------------------------- %% Function: %% handle_info(Info,StateName,State)-> {next_state, NextStateName, NextState}| @@ -1042,20 +1062,6 @@ init_private_key(PrivateKey, _, _,_) -> send_event(FsmPid, Event) -> gen_fsm:send_event(FsmPid, Event). -sync_send_event(FsmPid, Event, Timeout) -> - try gen_fsm:sync_send_event(FsmPid, Event, Timeout) of - Reply -> - Reply - catch - exit:{noproc, _} -> - {error, closed}; - exit:{timeout, _} -> - {error, timeout}; - exit:{normal, _} -> - {error, closed} - end. - - send_all_state_event(FsmPid, Event) -> gen_fsm:send_all_state_event(FsmPid, Event). @@ -1442,8 +1448,8 @@ encode_size_packet(Bin, Size, Max) -> false -> <> end. -encode_data(Data, Version, ConnectionStates) -> - ssl_record:encode_data(Data, Version, ConnectionStates). +encode_data(Data, Version, ConnectionStates, RenegotiateAt) -> + ssl_record:encode_data(Data, Version, ConnectionStates, RenegotiateAt). decode_alerts(Bin) -> decode_alerts(Bin, []). @@ -1454,6 +1460,20 @@ decode_alerts(<>, Acc) -> decode_alerts(<<>>, Acc) -> lists:reverse(Acc, []). +passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> + case Buffer of + <<>> -> + State = next_record(State0), + {next_state, StateName, State}; + _ -> + case application_data(<<>>, State0) of + Stop = {stop, _, _} -> + Stop; + State -> + {next_state, StateName, State} + end + end. + application_data(Data, #state{user_application = {_Mon, Pid}, socket_options = SOpts, bytes_to_read = BytesToRead, @@ -1594,13 +1614,56 @@ next_record(#state{tls_cipher_texts = [CT | Rest], gen_fsm:send_all_state_event(self(), Plain), State#state{tls_cipher_texts = Rest, connection_states = ConnStates}. + next_record_if_active(State = #state{socket_options = #socket_options{active = false}}) -> State; + next_record_if_active(State) -> next_record(State). +next_state_connection(#state{send_queue = Queue0, + negotiated_version = Version, + socket = Socket, + transport_cb = Transport, + connection_states = ConnectionStates0, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt} + } = State) -> + %% Send queued up data + case queue:out(Queue0) of + {{value, {From, Data}}, Queue} -> + case encode_data(Data, Version, ConnectionStates0, RenegotiateAt) of + {Msgs, [], ConnectionStates} -> + Result = Transport:send(Socket, Msgs), + gen_fsm:reply(From, Result), + next_state_connection(State#state{connection_states = ConnectionStates, + send_queue = Queue}); + %% This is unlikely to happen. User configuration of the + %% undocumented test option renegotiation_at can make it more likely. + {Msgs, RestData, ConnectionStates} -> + if + Msgs =/= [] -> + Transport:send(Socket, Msgs); + true -> + ok + end, + renegotiate(State#state{connection_states = ConnectionStates, + send_queue = queue:in_r({From, RestData})}) + end; + {empty, Queue0} -> + next_state_is_connection(State) + end. + +next_state_is_connection(State = + #state{recv_during_renegotiation = true, socket_options = + #socket_options{active = false}}) -> + passive_receive(State#state{recv_during_renegotiation = false}, connection); + +next_state_is_connection(State) -> + {next_state, connection, next_record_if_active(State)}. + + register_session(_, _, _, #session{is_resumable = true} = Session) -> Session; %% Already registered register_session(client, Host, Port, Session0) -> @@ -1650,7 +1713,10 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, bytes_to_read = 0, user_data_buffer = <<>>, log_alert = true, - session_cache_cb = SessionCacheCb + session_cache_cb = SessionCacheCb, + renegotiation = false, + recv_during_renegotiation = false, + send_queue = queue:new() }. sslsocket(Pid) -> @@ -1747,3 +1813,31 @@ handle_own_alert(Alert, Version, StateName, make_premaster_secret({MajVer, MinVer}) -> Rand = crypto:rand_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), <>. + + +ack_connection(#state{renegotiation = true}) -> + ok; +ack_connection(#state{renegotiation = false, from = From}) -> + gen_fsm:reply(From, connected). + + +renegotiate(#state{role = client} = State) -> + %% Handle same way as if server requested + %% the renegotiation + Hs0 = ssl_handshake:init_hashes(), + connection(#hello_request{}, State#state{tls_handshake_hashes = Hs0}); +renegotiate(#state{role = server, + socket = Socket, + transport_cb = Transport, + negotiated_version = Version, + connection_states = ConnectionStates0} = State) -> + HelloRequest = ssl_handshake:hello_request(), + Frag = ssl_handshake:encode_handshake(HelloRequest, Version, undefined), + Hs0 = ssl_handshake:init_hashes(), + {BinMsg, ConnectionStates} = + ssl_record:encode_handshake(Frag, Version, ConnectionStates0), + Transport:send(Socket, BinMsg), + {next_state, hello, next_record(State#state{connection_states = + ConnectionStates, + tls_handshake_hashes = Hs0, + renegotiation = true})}. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 8c598135ca..4e1c495fda 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -31,8 +31,8 @@ -include("ssl_debug.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([master_secret/4, client_hello/4, server_hello/3, hello/2, - certify/5, certificate/3, +-export([master_secret/4, client_hello/4, server_hello/3, hello/2, + hello_request/0, certify/5, certificate/3, client_certificate_verify/6, certificate_verify/6, certificate_request/2, key_exchange/2, finished/4, @@ -96,6 +96,15 @@ server_hello(SessionId, Version, ConnectionStates) -> session_id = SessionId }. +%%-------------------------------------------------------------------- +%% Function: hello_request() -> #hello_request{} +%% +%% Description: Creates a hello request message sent by server to +%% trigger renegotiation. +%%-------------------------------------------------------------------- +hello_request() -> + #hello_request{}. + %%-------------------------------------------------------------------- %% Function: hello(Hello, Info) -> %% {Version, Id, NewConnectionStates} | diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 23a5c93452..edb8d9741d 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -71,6 +71,7 @@ %% If false sessions will never be reused, if true they %% will be reused if possible. reuse_sessions, % boolean() + renegotiate_at, debug % }). diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index 37a3d1b639..da48f049f6 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -45,7 +45,7 @@ %% Encoding records -export([encode_handshake/3, encode_alert_record/3, - encode_change_cipher_spec/2, encode_data/3]). + encode_change_cipher_spec/2, encode_data/4]). %% Decoding -export([decode_cipher_text/2]). @@ -474,19 +474,31 @@ split_bin(Bin, ChunkSize, Acc) -> lists:reverse(Acc, [Bin]) end. -encode_data(Frag, Version, ConnectionStates) +encode_data(Frag, Version, ConnectionStates, RenegotiateAt) when byte_size(Frag) < (?MAX_PLAIN_TEXT_LENGTH - 2048) -> - encode_plain_text(?APPLICATION_DATA,Version,Frag,ConnectionStates); -encode_data(Frag, Version, ConnectionStates) -> + case encode_plain_text(?APPLICATION_DATA,Version,Frag,ConnectionStates, RenegotiateAt) of + {renegotiate, Data} -> + {[], Data, ConnectionStates}; + {Msg, CS} -> + {Msg, [], CS} + end; + +encode_data(Frag, Version, ConnectionStates, RenegotiateAt) when is_binary(Frag) -> Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH - 2048), - {CS1, Acc} = - lists:foldl(fun(B, {CS0, Acc}) -> - {ET, CS1} = - encode_plain_text(?APPLICATION_DATA, - Version, B, CS0), - {CS1, [ET | Acc]} - end, {ConnectionStates, []}, Data), - {lists:reverse(Acc), CS1}. + encode_data(Data, Version, ConnectionStates, RenegotiateAt); + +encode_data(Data, Version, ConnectionStates0, RenegotiateAt) when is_list(Data) -> + {ConnectionStates, EncodedMsg, NotEncdedData} = + lists:foldl(fun(B, {CS0, Encoded, Rest}) -> + case encode_plain_text(?APPLICATION_DATA, + Version, B, CS0, RenegotiateAt) of + {renegotiate, NotEnc} -> + {CS0, Encoded, [NotEnc | Rest]}; + {Enc, CS1} -> + {CS1, [Enc | Encoded], Rest} + end + end, {ConnectionStates0, [], []}, Data), + {lists:reverse(EncodedMsg), lists:reverse(NotEncdedData), ConnectionStates}. encode_handshake(Frag, Version, ConnectionStates) -> encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). @@ -499,6 +511,16 @@ encode_alert_record(#alert{level = Level, description = Description}, encode_change_cipher_spec(Version, ConnectionStates) -> encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). +encode_plain_text(Type, Version, Data, ConnectionStates, RenegotiateAt) -> + #connection_states{current_write = + #connection_state{sequence_number = Num}} = ConnectionStates, + case renegotiate(Num, RenegotiateAt) of + false -> + encode_plain_text(Type, Version, Data, ConnectionStates); + true -> + {renegotiate, Data} + end. + encode_plain_text(Type, Version, Data, ConnectionStates) -> #connection_states{current_write=#connection_state{ compression_state=CompS0, @@ -511,6 +533,11 @@ encode_plain_text(Type, Version, Data, ConnectionStates) -> CTBin = encode_tls_cipher_text(Type, Version, CipherText), {CTBin, ConnectionStates#connection_states{current_write = CS2}}. +renegotiate(N, M) when N < M-> + false; +renegotiate(_,_) -> + true. + encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment) -> Length = erlang:iolist_size(Fragment), [<>, Fragment]. @@ -529,9 +556,6 @@ cipher(Type, Version, Fragment, CS0) -> CS2 = CS1#connection_state{cipher_state=CipherS1}, {Ciphered, CS2}. -decipher(TLS=#ssl_tls{type = ?CHANGE_CIPHER_SPEC}, CS) -> - %% These are never encrypted - {TLS, CS}; decipher(TLS=#ssl_tls{type=Type, version=Version, fragment=Fragment}, CS0) -> SP = CS0#connection_state.security_parameters, BCA = SP#security_parameters.bulk_cipher_algorithm, % eller Cipher? diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index 7370e0f0b3..362b7039d4 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -63,6 +63,13 @@ sequence_number }). +-define(MAX_SEQENCE_NUMBER, 18446744073709552000). %% math:pow(2, 64) - 1 = 1.8446744073709552e19 +%% Sequence numbers can not wrap so when max is about to be reached we should renegotiate. +%% We will renegotiate a little before so that there will be sequence numbers left +%% for the rehandshake and a little data. +-define(MARGIN, 100). +-define(DEFAULT_RENEGOTIATE_AT, ?MAX_SEQENCE_NUMBER - ?MARGIN). + %% ConnectionEnd -define(SERVER, 0). -define(CLIENT, 1). diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 3d9cec43dd..c79cfdea3f 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -30,6 +30,7 @@ -define('24H_in_sec', 86400). -define(TIMEOUT, 60000). -define(EXPIRE, 10). +-define(SLEEP, 500). -behaviour(ssl_session_cache_api). @@ -162,7 +163,9 @@ all(suite) -> server_verify_no_cacerts, client_verify_none_passive, client_verify_none_active, client_verify_none_active_once %%, session_cache_process_list, session_cache_process_mnesia - ,reuse_session, reuse_session_expired, server_does_not_want_to_reuse_session + ,reuse_session, reuse_session_expired, server_does_not_want_to_reuse_session, + client_renegotiate, server_renegotiate, + client_no_wrap_sequence_number, server_no_wrap_sequence_number ]. %% Test cases starts here. @@ -267,7 +270,7 @@ controlling_process_result(Socket, Pid, Msg) -> ok = ssl:controlling_process(Socket, Pid), %% Make sure other side has evaluated controlling_process %% before message is sent - test_server:sleep(100), + test_server:sleep(?SLEEP), ssl:send(Socket, Msg), no_result_msg. @@ -298,7 +301,7 @@ controller_dies(Config) when is_list(Config) -> {options, ClientOpts}]), test_server:format("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), - timer:sleep(200), %% so that they are connected + test_server:sleep(?SLEEP), %% so that they are connected process_flag(trap_exit, true), @@ -307,7 +310,7 @@ controller_dies(Config) when is_list(Config) -> get_close(Client, ?LINE), %% Test that clients die when process disappear - Server ! listen, timer:sleep(200), + Server ! listen, test_server:sleep(?SLEEP), Tester = self(), Connect = fun(Pid) -> {ok, Socket} = ssl:connect(Hostname, Port, @@ -321,7 +324,7 @@ controller_dies(Config) when is_list(Config) -> get_close(Client2, ?LINE), %% Test that clients die when the controlling process have changed - Server ! listen, timer:sleep(200), + Server ! listen, test_server:sleep(?SLEEP), Client3 = spawn_link(fun() -> Connect(Tester) end), Controller = spawn_link(fun() -> receive die_nice -> normal end end), @@ -345,7 +348,7 @@ controller_dies(Config) when is_list(Config) -> get_close(Controller, ?LINE), %% Test that servers die - Server ! listen, timer:sleep(200), + Server ! listen, test_server:sleep(?SLEEP), LastClient = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, @@ -353,7 +356,7 @@ controller_dies(Config) when is_list(Config) -> controller_dies_result, [self(), ClientMsg]}}, {options, [{reuseaddr,true}|ClientOpts]}]), - timer:sleep(200), %% so that they are connected + test_server:sleep(?SLEEP), %% so that they are connected exit(Server, killed), get_close(Server, ?LINE), @@ -667,10 +670,10 @@ send_close(Config) when is_list(Config) -> test_server:format("Testcase ~p, Client ~p Server ~p ~n", [self(), self(), Server]), - ok = ssl:send(SslS, "HejHopp"), - {ok,<<"Hejhopp">>} = ssl:recv(SslS, 7), + ok = ssl:send(SslS, "Hello world"), + {ok,<<"Hello world">>} = ssl:recv(SslS, 11), gen_tcp:close(TcpS), - {error, _} = ssl:send(SslS, "HejHopp"), + {error, _} = ssl:send(SslS, "Hello world"), ssl_test_lib:close(Server). %%-------------------------------------------------------------------- @@ -710,11 +713,11 @@ upgrade(Config) when is_list(Config) -> ssl_test_lib:close(Client). upgrade_result(Socket) -> - ok = ssl:send(Socket, "Hejhopp"), + 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, _, <<"Hejhopp">>} -> + {ssl, _, <<"Hello world">>} -> ok end. @@ -957,7 +960,7 @@ eoptions(Config) when is_list(Config) -> ssl_test_lib:check_result(Server0, {error, {eoptions, {active,trice}}}, Client0, {error, {eoptions, {active,trice}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), Server1 = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, @@ -971,7 +974,7 @@ eoptions(Config) when is_list(Config) -> ssl_test_lib:check_result(Server1, {error, {eoptions, {header, a}}}, Client1, {error, {eoptions, {header, a}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), Server2 = @@ -988,7 +991,7 @@ eoptions(Config) when is_list(Config) -> Client2, {error, {eoptions, {mode, a}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), Server3 = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, @@ -1002,7 +1005,7 @@ eoptions(Config) when is_list(Config) -> ssl_test_lib:check_result(Server3, {error, {eoptions, {packet, 8.0}}}, Client3, {error, {eoptions, {packet, 8.0}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), %% ssl Server4 = @@ -1017,7 +1020,7 @@ eoptions(Config) when is_list(Config) -> ssl_test_lib:check_result(Server4, {error, {eoptions, {verify, 4}}}, Client4, {error, {eoptions, {verify, 4}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), Server5 = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, @@ -1031,7 +1034,7 @@ eoptions(Config) when is_list(Config) -> ssl_test_lib:check_result(Server5, {error, {eoptions, {depth, four}}}, Client5, {error, {eoptions, {depth, four}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), Server6 = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, @@ -1046,7 +1049,7 @@ eoptions(Config) when is_list(Config) -> Client6, {error, {eoptions, {cacertfile, ""}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), Server7 = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, @@ -1061,7 +1064,7 @@ eoptions(Config) when is_list(Config) -> {error, {eoptions, {certfile, 'cert.pem'}}}, Client7, {error, {eoptions, {certfile, 'cert.pem'}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), Server8 = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, @@ -1076,7 +1079,7 @@ eoptions(Config) when is_list(Config) -> {error, {eoptions, {keyfile, 'key.pem'}}}, Client8, {error, {eoptions, {keyfile, 'key.pem'}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), Server9 = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, @@ -1091,7 +1094,7 @@ eoptions(Config) when is_list(Config) -> Client9, {error, {eoptions, {key, 'key.pem'}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), Server10 = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, @@ -1105,7 +1108,7 @@ eoptions(Config) when is_list(Config) -> ssl_test_lib:check_result(Server10, {error, {eoptions, {password, foo}}}, Client10, {error, {eoptions, {password, foo}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), %% Misc Server11 = @@ -1121,7 +1124,7 @@ eoptions(Config) when is_list(Config) -> Client11, {error, {eoptions, {ssl_imp, cool}}}), - test_server:sleep(500), + test_server:sleep(?SLEEP), Server12 = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, @@ -1203,7 +1206,7 @@ shutdown_write(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, {error, closed}). shutdown_write_result(Socket, server) -> - test_server:sleep(500), + test_server:sleep(?SLEEP), ssl:shutdown(Socket, write); shutdown_write_result(Socket, client) -> ssl:recv(Socket, 0). @@ -1233,7 +1236,7 @@ shutdown_both(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, {error, closed}). shutdown_both_result(Socket, server) -> - test_server:sleep(500), + test_server:sleep(?SLEEP), ssl:shutdown(Socket, read_write); shutdown_both_result(Socket, client) -> ssl:recv(Socket, 0). @@ -1339,7 +1342,7 @@ reuse_session(Config) when is_list(Config) -> Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, no_result, []}}, + {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, {options, ClientOpts}]), SessionInfo = receive @@ -1350,7 +1353,7 @@ reuse_session(Config) when is_list(Config) -> Server ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client1 = ssl_test_lib:start_client([{node, ClientNode}, @@ -1410,7 +1413,7 @@ reuse_session(Config) when is_list(Config) -> Server1 ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client4 = ssl_test_lib:start_client([{node, ClientNode}, @@ -1457,7 +1460,7 @@ reuse_session_expired(Config) when is_list(Config) -> Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, no_result, []}}, + {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, {options, ClientOpts}]), SessionInfo = receive @@ -1468,7 +1471,7 @@ reuse_session_expired(Config) when is_list(Config) -> Server ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client1 = ssl_test_lib:start_client([{node, ClientNode}, @@ -1530,7 +1533,7 @@ server_does_not_want_to_reuse_session(Config) when is_list(Config) -> Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, no_result, []}}, + {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, {options, ClientOpts}]), SessionInfo = receive @@ -1541,7 +1544,7 @@ server_does_not_want_to_reuse_session(Config) when is_list(Config) -> Server ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client1 = ssl_test_lib:start_client([{node, ClientNode}, @@ -1849,31 +1852,203 @@ client_verify_none_active_once(Config) when is_list(Config) -> ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_renegotiate(doc) -> + ["Test ssl:renegotiate/1 on client."]; + +client_renegotiate(suite) -> + []; + +client_renegotiate(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(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), + + test_server:sleep(?SLEEP), + + 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), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- +server_renegotiate(doc) -> + ["Test ssl:renegotiate/1 on server."]; + +server_renegotiate(suite) -> + []; + +server_renegotiate(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(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, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + test_server:sleep(?SLEEP), + + 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), + ok. + +%%-------------------------------------------------------------------- +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(suite) -> + []; + +client_no_wrap_sequence_number(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to erlang", + N = 10, + + 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), + + test_server:sleep(?SLEEP), + + 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), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- +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(suite) -> + []; + +server_no_wrap_sequence_number(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang 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} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + test_server:sleep(?SLEEP), + + 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), + ok. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- send_recv_result(Socket) -> - ssl:send(Socket, "Hejhopp"), - test_server:sleep(100), - {ok,"Hejhopp"} = ssl:recv(Socket, 7), + ssl:send(Socket, "Hello world"), + test_server:sleep(?SLEEP), + {ok,"Hello world"} = ssl:recv(Socket, 11), ok. send_recv_result_active(Socket) -> - ssl:send(Socket, "Hejhopp"), - test_server:sleep(100), + ssl:send(Socket, "Hello world"), + test_server:sleep(?SLEEP), receive - {ssl, Socket, "Hejhopp"} -> + {ssl, Socket, "Hello world"} -> ok end. send_recv_result_active_once(Socket) -> - ssl:send(Socket, "Hejhopp"), - test_server:sleep(100), + ssl:send(Socket, "Hello world"), + test_server:sleep(?SLEEP), receive - {ssl, Socket, "Hejhopp"} -> + {ssl, Socket, "Hello world"} -> ok end. + +renegotiate(Socket, Data) -> + [{session_id, Id} | _ ] = ssl:session_info(Socket), + ssl:renegotiate(Socket), + ssl:send(Socket, Data), + test_server:sleep(1000), + case ssl:session_info(Socket) of + [{session_id, Id} | _ ] -> + fail_session_not_renegotiated; + _ -> + ok + end. + + session_cache_process_list(doc) -> ["Test reuse of sessions (short handshake)"]; @@ -1909,7 +2084,7 @@ session_cache_process(Type,Config) when is_list(Config) -> Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, no_result, []}}, + {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, {options, ClientOpts}]), SessionInfo = receive @@ -1920,7 +2095,7 @@ session_cache_process(Type,Config) when is_list(Config) -> Server ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client1 = ssl_test_lib:start_client([{node, ClientNode}, @@ -1963,7 +2138,7 @@ session_cache_process(Type,Config) when is_list(Config) -> Server1 ! listen, %% Make sure session is registered - test_server:sleep(500), + test_server:sleep(?SLEEP), Client4 = ssl_test_lib:start_client([{node, ClientNode}, @@ -2112,3 +2287,14 @@ session_loop(Sess) -> session_loop(Sess) end. +erlang_ssl_receive(Socket, Data) -> + receive + {ssl, Socket, Data} -> + io:format("Received ~p~n",[Data]), + ok; + Other -> + test_server:fail({unexpected_message, Other}) + after 4000 -> + test_server:fail({did_not_get, Data}) + end. + diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 2df2e70679..f985058cd7 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -71,13 +71,9 @@ run_server(Opts) -> run_server(ListenSocket, Opts). run_server(ListenSocket, Opts) -> + AcceptSocket = connect(ListenSocket, Opts), Node = proplists:get_value(node, Opts), Pid = proplists:get_value(from, Opts), - test_server:format("ssl:transport_accept(~p)~n", [ListenSocket]), - {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, - [ListenSocket]), - test_server:format("ssl:ssl_accept(~p)~n", [AcceptSocket]), - ok = rpc:call(Node, ssl, ssl_accept, [AcceptSocket]), {Module, Function, Args} = proplists:get_value(mfa, Opts), test_server:format("Server: apply(~p,~p,~p)~n", [Module, Function, [AcceptSocket | Args]]), @@ -85,6 +81,7 @@ run_server(ListenSocket, Opts) -> no_result_msg -> ok; Msg -> + test_server:format("Msg: ~p ~n", [Msg]), Pid ! {self(), Msg} end, receive @@ -94,6 +91,38 @@ run_server(ListenSocket, Opts) -> ok = rpc:call(Node, ssl, close, [AcceptSocket]) end. +%%% To enable to test with s_client -reconnect +connect(ListenSocket, Opts) -> + Node = proplists:get_value(node, Opts), + ReconnectTimes = proplists:get_value(reconnect_times, Opts, 0), + AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy), + case ReconnectTimes of + 0 -> + AcceptSocket; + _ -> + remove_close_msg(ReconnectTimes), + AcceptSocket + end. + +connect(_, _, 0, AcceptSocket) -> + AcceptSocket; +connect(ListenSocket, Node, N, _) -> + test_server:format("ssl:transport_accept(~p)~n", [ListenSocket]), + {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, + [ListenSocket]), + test_server:format("ssl:ssl_accept(~p)~n", [AcceptSocket]), + ok = rpc:call(Node, ssl, ssl_accept, [AcceptSocket]), + connect(ListenSocket, Node, N-1, AcceptSocket). + +remove_close_msg(0) -> + ok; +remove_close_msg(ReconnectTimes) -> + receive + {ssl_closed, _} -> + remove_close_msg(ReconnectTimes -1) + end. + + start_client(Args) -> spawn_link(?MODULE, run_client, [Args]). @@ -410,3 +439,21 @@ do_inet_port(Node) -> no_result(_) -> no_result_msg. + +trigger_renegotiate(Socket, [ErlData, N]) -> + [{session_id, Id} | _ ] = ssl:session_info(Socket), + trigger_renegotiate(Socket, ErlData, N, Id). + +trigger_renegotiate(Socket, _, 0, Id) -> + test_server:sleep(1000), + case ssl:session_info(Socket) of + [{session_id, Id} | _ ] -> + fail_session_not_renegotiated; + _ -> + ok + end; + +trigger_renegotiate(Socket, ErlData, N, Id) -> + ssl:send(Socket, ErlData), + trigger_renegotiate(Socket, ErlData, N-1, Id). + diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index c079e12b83..adb5b9cd13 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -30,6 +30,8 @@ -define(TIMEOUT, 120000). -define(SLEEP, 1000). +-define(OPENSSL_RENEGOTIATE, "r\n"). +-define(OPENSSL_QUIT, "Q\n"). %% Test server callback functions %%-------------------------------------------------------------------- @@ -114,6 +116,11 @@ all(doc) -> all(suite) -> [erlang_client_openssl_server, erlang_server_openssl_client, + erlang_server_openssl_client_reuse_session, + erlang_client_openssl_server_renegotiate, + erlang_client_openssl_server_no_wrap_sequence_number, + erlang_server_openssl_client_no_wrap_sequence_number, + erlang_client_openssl_server_no_server_ca_cert, ssl3_erlang_client_openssl_server, ssl3_erlang_server_openssl_client, ssl3_erlang_client_openssl_server_client_cert, @@ -148,7 +155,7 @@ erlang_client_openssl_server(Config) when is_list(Config) -> KeyFile = proplists:get_value(keyfile, ServerOpts), Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile, + " -cert " ++ CertFile ++ " -key " ++ KeyFile, test_server:format("openssl cmd: ~p~n", [Cmd]), @@ -211,6 +218,239 @@ erlang_server_openssl_client(Config) when is_list(Config) -> process_flag(trap_exit, false), ok. +%%-------------------------------------------------------------------- + +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(suite) -> + []; +erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + + {_, ServerNode, _} = 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), + + test_server:sleep(?SLEEP), + + Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ + " -host localhost -reconnect", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, ok), + + ssl_test_lib:close(Server), + + 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(suite) -> + []; +erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_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), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -msg", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + test_server:sleep(?SLEEP), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + delayed_send, [[ErlData, OpenSslData]]}}, + {options, ClientOpts}]), + test_server:sleep(?SLEEP), + + port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + + test_server:sleep(?SLEEP), + + port_command(OpensslPort, OpenSslData), + + ssl_test_lib:check_result(Client, ok), + + %% Clean close down! Server needs to be closed first !! + close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. + +%%-------------------------------------------------------------------- + +erlang_client_openssl_server_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."]; +erlang_client_openssl_server_no_wrap_sequence_number(suite) -> + []; +erlang_client_openssl_server_no_wrap_sequence_number(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl", + N = 10, + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -msg", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + test_server:sleep(?SLEEP), + + 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 !! + close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- +erlang_server_openssl_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."]; + +erlang_server_openssl_client_no_wrap_sequence_number(suite) -> + []; +erlang_server_openssl_client_no_wrap_sequence_number(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + + {_, ServerNode, _} = 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} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + test_server:sleep(?SLEEP), + + Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ + " -host localhost -msg", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, ok), + + ssl_test_lib:close(Server), + + close_port(OpenSslPort), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- + +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(suite) -> + []; +erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts = ?config(client_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), + + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -msg", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + test_server:sleep(?SLEEP), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + erlang_ssl_receive, [Data]}}, + {options, ClientOpts}]), + + port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok), + + %% Clean close down! Server needs to be closed first !! + close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. + %%-------------------------------------------------------------------- ssl3_erlang_client_openssl_server(doc) -> ["Test erlang client with openssl server"]; @@ -300,8 +540,8 @@ ssl3_erlang_client_openssl_server_client_cert(Config) when is_list(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), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ @@ -439,8 +679,7 @@ tls1_erlang_client_openssl_server(Config) when is_list(Config) -> KeyFile = proplists:get_value(keyfile, ServerOpts), Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ - " -cert " ++ CertFile - ++ " -key " ++ KeyFile ++ " -tls1", + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -tls1", test_server:format("openssl cmd: ~p~n", [Cmd]), @@ -668,8 +907,7 @@ cipher(CipherSuite, Version, Config) -> KeyFile = proplists:get_value(keyfile, ServerOpts), Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ - " -cert " ++ CertFile - ++ " -key " ++ KeyFile ++ "", + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "", test_server:format("openssl cmd: ~p~n", [Cmd]), @@ -738,8 +976,14 @@ connection_info(Socket, Version) -> connection_info_result(Socket) -> ssl:connection_info(Socket). + +delayed_send(Socket, [ErlData, OpenSslData]) -> + test_server:sleep(?SLEEP), + ssl:send(Socket, ErlData), + erlang_ssl_receive(Socket, OpenSslData). + close_port(Port) -> - port_command(Port, "Q\n"), + port_command(Port, ?OPENSSL_QUIT), %%catch port_command(Port, "quit\n"), close_loop(Port, 500, false). -- cgit v1.2.3