diff options
| -rw-r--r-- | lib/ssl/src/ssl.erl | 19 | ||||
| -rw-r--r-- | lib/ssl/src/ssl_connection.erl | 314 | ||||
| -rw-r--r-- | lib/ssl/src/ssl_handshake.erl | 13 | ||||
| -rw-r--r-- | lib/ssl/src/ssl_internal.hrl | 11 | ||||
| -rw-r--r-- | lib/ssl/src/ssl_record.erl | 64 | ||||
| -rw-r--r-- | lib/ssl/src/ssl_record.hrl | 17 | ||||
| -rw-r--r-- | lib/ssl/test/ssl_basic_SUITE.erl | 276 | ||||
| -rw-r--r-- | lib/ssl/test/ssl_test_lib.erl | 57 | ||||
| -rw-r--r-- | lib/ssl/test/ssl_to_openssl_SUITE.erl | 268 | 
9 files changed, 833 insertions, 206 deletions
| 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 -> <<Len:Size, Bin/binary>>      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(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, 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),      <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>. + + +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, @@ -97,6 +97,15 @@ server_hello(SessionId, Version, ConnectionStates) ->  		 }.  %%-------------------------------------------------------------------- +%% 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} |  %%                                   #alert{} 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),      [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, 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). | 
