Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: erlang/otp
base: master
...
head fork: klyr/otp
compare: ssl_sni
Checking mergeability… Don't worry, you can still create the pull request.
  • 2 commits
  • 11 files changed
  • 1 commit comment
  • 1 contributor
View
48 lib/ssl/doc/src/ssl.xml
@@ -44,6 +44,8 @@
U.S. lifted its export restrictions in early 2000.</item>
<item>CRL and policy certificate
extensions are not supported yet. </item>
+ <item>Support for 'Server Name Indication' extension
+ (RFC 6066 section 3).</item>
</list>
</section>
@@ -75,7 +77,8 @@
{keyfile, path()} | {password, string()} |
{cacerts, [der_encoded()]} | {cacertfile, path()} |
|{dh, der_encoded()} | {dhfile, path()} | {ciphers, ciphers()} |
- {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()}
+ {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()} |
+ {sni_hosts, [{hostname(), [ssloption()]}]}
</c></p>
<p><c>transportoption() = {CallbackModule, DataTag, ClosedTag}
@@ -351,6 +354,35 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} |
and CipherSuite is of type ciphersuite().
</item>
+ <tag>{sni_hosts, [{hostname(), [ssloption()]}]}</tag>
+ <item>Enable Server Name Indication server support. Allows the server to
+ use different SSL parameters based on SNI servername specified by
+ client. For example a server can present multiple certificates on the same
+ ip address and port number. This is a kind of virtualhost for SSL
+ connections. Use the <c>ssl:sni_hostname/1</c> function to get the
+ selected host. Overridable options are <c>verify</c>, <c>verify_fun</c>,
+ <c>fail_if_no_peer_cert</c>, <c>depth</c>, <c>cert</c>, <c>certfile</c>,
+ <c>key</c>, <c>keyfile</c>, <c>password</c>, <c>cacerts</c>,
+ <c>cacertfile</c>, <c>dhfile</c>, <c>ciphers</c>, <c>reuse_sessions</c>,
+ <c>reuse_session</c>.
+
+ Usage example:
+ <code>
+ {ok, ListenSocket} = ssl:listen(Port, [
+ {certfile, "default.crt"},
+ {keyfile, "default.key"},
+ {verify, verify_none},
+ {sni_hosts,
+ [{"vhost1.example.com",
+ [{certfile, "vhost1.example.com.crt"},
+ {keyfile, "vhost1.example.com.key"}]},
+ {"vhost2.example.org",
+ [{verify, verify_peer}]}]}]),
+ {ok, TS} = ssl:transport_accept(ListenSocket),
+ S = ssl:ssl_accept(TS),
+ SelectedHost = ssl:sni_hostname(S).
+ </code>
+ </item>
</taglist>
</section>
@@ -679,6 +711,20 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} |
</func>
<func>
+ <name>sni_hostname(Socket) -> undefined | list()</name>
+ <fsummary>Returns the hostname selected by the server if SNI extension was
+ used. Else return undefined, meaning that the default configuration has
+ been choosen</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ </type>
+ <desc>
+ <p>Use this function when the SSL handshake is finished to know what was
+ the requested SNI hostname.</p>
+ </desc>
+ </func>
+
+ <func>
<name>sockname(Socket) -> {ok, {Address, Port}} |
{error, Reason}</name>
<fsummary>Return the local address and port.</fsummary>
View
20 lib/ssl/src/ssl.erl
@@ -30,7 +30,7 @@
controlling_process/2, listen/2, pid/1, peername/1, peercert/1,
recv/2, recv/3, send/2, getopts/2, setopts/2, sockname/1,
versions/0, session_info/1, format_error/1,
- renegotiate/1, prf/5]).
+ renegotiate/1, prf/5, sni_hostname/1]).
-deprecated({pid, 1, next_major_release}).
@@ -191,7 +191,7 @@ transport_accept(#sslsocket{pid = {ListenSocket, #config{cb=CbInfo, ssl=SslOpts}
{ok, Socket} ->
ok = inet:setopts(ListenSocket, InetValues),
{ok, Port} = inet:port(Socket),
- ConnArgs = [server, "localhost", Port, Socket,
+ ConnArgs = [server, undefined, Port, Socket,
{SslOpts, socket_options(InetValues)}, self(), CbInfo],
case ssl_connection_sup:start_child(ConnArgs) of
{ok, Pid} ->
@@ -431,6 +431,13 @@ prf(#sslsocket{pid = Pid, fd = new_ssl},
Secret, Label, Seed, WantedLength) ->
ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength).
+%%
+%% Description: Returns the selected server name choosen by the server, or
+%% returns undefined if the default configuration was choosen
+%% --------------------------------------------------------------------
+sni_hostname(#sslsocket{pid = Pid, fd = new_ssl}) ->
+ ssl_connection:sni_hostname(Pid).
+
%%---------------------------------------------------------------
-spec format_error({error, term()}) -> list().
%%
@@ -533,6 +540,7 @@ handle_options(Opts0, _Role) ->
end,
CertFile = handle_option(certfile, Opts, ""),
+ Hosts = handle_option(sni_hosts, Opts, []),
SSLOptions = #ssl_options{
versions = handle_option(versions, Opts, []),
@@ -558,7 +566,8 @@ handle_options(Opts0, _Role) ->
renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT),
debug = handle_option(debug, Opts, []),
hibernate_after = handle_option(hibernate_after, Opts, undefined),
- erl_dist = handle_option(erl_dist, Opts, false)
+ erl_dist = handle_option(erl_dist, Opts, false),
+ sni_hosts = Hosts
},
CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
@@ -567,7 +576,8 @@ handle_options(Opts0, _Role) ->
depth, cert, certfile, key, keyfile,
password, cacerts, cacertfile, dh, dhfile, ciphers,
debug, reuse_session, reuse_sessions, ssl_imp,
- cb_info, renegotiate_at, secure_renegotiate, hibernate_after, erl_dist],
+ cb_info, renegotiate_at, secure_renegotiate, hibernate_after, erl_dist,
+ sni_hosts],
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
@@ -682,6 +692,8 @@ validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 ->
validate_option(erl_dist,Value) when Value == true;
Value == false ->
Value;
+validate_option(sni_hosts, Value) when is_list(Value) ->
+ Value;
validate_option(Opt, Value) ->
throw({error, {eoptions, {Opt, Value}}}).
View
110 lib/ssl/src/ssl_connection.erl
@@ -41,7 +41,7 @@
socket_control/3, 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, renegotiation/1,
- prf/5]).
+ prf/5, sni_hostname/1]).
%% Called by ssl_connection_sup
-export([start_link/7]).
@@ -151,7 +151,7 @@ connect(Host, Port, Socket, Options, User, CbInfo, Timeout) ->
%% ssl handshake.
%%--------------------------------------------------------------------
ssl_accept(Port, Socket, Opts, User, CbInfo, Timeout) ->
- try start_fsm(server, "localhost", Port, Socket, Opts, User,
+ try start_fsm(server, undefined, Port, Socket, Opts, User,
CbInfo, Timeout)
catch
exit:{noproc, _} ->
@@ -284,6 +284,12 @@ renegotiation(ConnectionPid) ->
prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
sync_send_all_state_event(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}).
+%%
+%% Description: Returns the selected server name choosen by the server, or
+%% returns undefined if the default configuration was choosen
+sni_hostname(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, sni_hostname).
+
%%====================================================================
%% ssl_connection_sup API
%%====================================================================
@@ -403,7 +409,7 @@ hello(#server_hello{cipher_suite = CipherSuite,
{stop, normal, State0}
end;
-hello(Hello = #client_hello{client_version = ClientVersion},
+hello(Hello = #client_hello{client_version = ClientVersion, sni = undefined},
State = #state{connection_states = ConnectionStates0,
port = Port, session = #session{own_certificate = Cert} = Session0,
renegotiation = {Renegotiation, _},
@@ -422,6 +428,35 @@ hello(Hello = #client_hello{client_version = ClientVersion},
{stop, normal, State}
end;
+hello(Hello = #client_hello{sni = SNI}, State0) ->
+ {Hostname, SslOpts} = update_ssl_options(SNI, State0#state.ssl_options),
+ case Hostname of
+ undefined ->
+ hello(Hello#client_hello{sni = undefined}, State0);
+ _ ->
+ %% redo ssl initialization with new parameters for the specified
+ %% hostname
+ try ssl_init(SslOpts, State0#state.role) of
+ {ok, Ref, CertDbHandle, CacheHandle, OwnCert, Key, DHParams} ->
+ Session = State0#state.session,
+ State = State0#state{
+ ssl_options = SslOpts#ssl_options{
+ password = undefined},
+ session = Session#session{
+ own_certificate = OwnCert},
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ session_cache = CacheHandle,
+ private_key = Key,
+ diffie_hellman_params = DHParams,
+ host = Hostname},
+ hello(Hello#client_hello{sni = undefined}, State)
+ catch
+ throw:Error ->
+ {stop, Error}
+ end
+ end;
+
hello(timeout, State) ->
{ next_state, hello, State, hibernate };
@@ -923,7 +958,10 @@ handle_sync_event(session_info, _, StateName,
handle_sync_event(peer_certificate, _, StateName,
#state{session = #session{peer_certificate = Cert}}
= State) ->
- {reply, {ok, Cert}, StateName, State, get_timeout(State)}.
+ {reply, {ok, Cert}, StateName, State, get_timeout(State)};
+
+handle_sync_event(sni_hostname, _, StateName, #state{host = Host} = State) ->
+ {reply, Host, StateName, State, get_timeout(State)}.
%%--------------------------------------------------------------------
%% Description: This function is called by a gen_fsm when it receives any
@@ -1263,12 +1301,20 @@ verify_client_cert(#state{client_certificate_requested = false} = State) ->
do_server_hello(Type, #state{negotiated_version = Version,
session = #session{session_id = SessId} = Session,
connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}}
+ renegotiation = {Renegotiation, _},
+ host = SNI0}
= State0) when is_atom(Type) ->
+ %% When resuming a session, the server MUST NOT include a server_name
+ %% extension in the server hello.
+ SNI = case Type of
+ new -> SNI0;
+ resumed -> undefined
+ end,
+
ServerHello =
ssl_handshake:server_hello(SessId, Version,
- ConnectionStates0, Renegotiation),
+ ConnectionStates0, Renegotiation, SNI),
State1 = server_hello(ServerHello, State0),
case Type of
@@ -1323,10 +1369,10 @@ handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0} =
handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,
negotiated_version = Version,
- host = Host, port = Port,
+ host = Host, port = Port, role = Role,
session_cache = Cache,
session_cache_cb = CacheCb} = State0) ->
- Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
+ Session = CacheCb:lookup(Cache, {{Role, Host, Port}, SessId}),
case ssl_handshake:master_secret(Version, Session,
ConnectionStates0, client) of
{_, ConnectionStates1} ->
@@ -2021,21 +2067,15 @@ next_state_is_connection(StateName, State0) ->
public_key_info = undefined,
tls_handshake_hashes = {<<>>, <<>>}}).
-register_session(client, Host, Port, #session{is_resumable = new} = Session0) ->
- Session = Session0#session{is_resumable = true},
- ssl_manager:register_session(Host, Port, Session),
- Session;
-register_session(server, _, Port, #session{is_resumable = new} = Session0) ->
+register_session(Role, Host, Port, #session{is_resumable = new} = Session0) ->
Session = Session0#session{is_resumable = true},
- ssl_manager:register_session(Port, Session),
+ ssl_manager:register_session(Role, Host, Port, Session),
Session;
register_session(_, _, _, Session) ->
Session. %% Already registered
-invalidate_session(client, Host, Port, Session) ->
- ssl_manager:invalidate_session(Host, Port, Session);
-invalidate_session(server, _, Port, Session) ->
- ssl_manager:invalidate_session(Port, Session).
+invalidate_session(Role, Host, Port, Session) ->
+ ssl_manager:invalidate_session(Role, Host, Port, Session).
initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
{CbModule, DataTag, CloseTag, ErrorTag}) ->
@@ -2363,3 +2403,37 @@ get_timeout(#state{ssl_options=#ssl_options{hibernate_after=undefined}}) ->
infinity;
get_timeout(#state{ssl_options=#ssl_options{hibernate_after=HibernateAfter}}) ->
HibernateAfter.
+
+%% Override main ssl options with sni host options if present
+update_ssl_options(undefined, SslOpts) ->
+ {undefined, SslOpts#ssl_options{sni_hosts = undefined}};
+update_ssl_options(Hostname, SslOpts) ->
+ case proplists:get_value(Hostname, SslOpts#ssl_options.sni_hosts,
+ undefined) of
+ undefined ->
+ {undefined, SslOpts#ssl_options{sni_hosts = undefined}};
+ HostConf ->
+ %% Overridable options when sni hostname is specified
+ NewOpts =
+ lists:foldl(
+ fun({verify, Verify}, AccOpts) -> AccOpts#ssl_options{verify = Verify};
+ ({verify_fun, VerifyFun}, AccOpts) -> AccOpts#ssl_options{verify_fun = VerifyFun};
+ ({fail_if_no_peer_cert, Fail}, AccOpts) -> AccOpts#ssl_options{fail_if_no_peer_cert = Fail};
+ ({depth, Depth}, AccOpts) -> AccOpts#ssl_options{depth = Depth};
+ ({cert, Cert}, AccOpts) -> AccOpts#ssl_options{cert = Cert};
+ ({certfile, Certfile}, AccOpts) -> AccOpts#ssl_options{certfile = Certfile};
+ ({key, Key}, AccOpts) -> AccOpts#ssl_options{key = Key};
+ ({keyfile, Keyfile}, AccOpts) -> AccOpts#ssl_options{keyfile = Keyfile};
+ ({password, Password}, AccOpts) -> AccOpts#ssl_options{password = Password};
+ ({cacerts, CaCerts}, AccOpts) -> AccOpts#ssl_options{cacerts = CaCerts};
+ ({cacertfile, CaCertFile}, AccOpts) -> AccOpts#ssl_options{cacertfile = CaCertFile};
+ ({dhfile, DHFile}, AccOpts) -> AccOpts#ssl_options{dhfile = DHFile};
+ ({ciphers, Ciphers}, AccOpts) -> AccOpts#ssl_options{ciphers = Ciphers};
+ ({reuse_sessions, Reuse}, AccOpts) -> AccOpts#ssl_options{reuse_sessions = Reuse};
+ ({reuse_session, ReuseFun}, AccOpts) -> AccOpts#ssl_options{reuse_session = ReuseFun};
+ ({Opt, Value}, _) -> throw({error, {eoptions, {Opt, Value}}})
+ end,
+ SslOpts, HostConf),
+ %% Remove sni_hosts options
+ {Hostname, NewOpts#ssl_options{sni_hosts = undefined}}
+ end.
View
120 lib/ssl/src/ssl_handshake.erl
@@ -30,7 +30,7 @@
-include("ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([master_secret/4, client_hello/6, server_hello/4, hello/4,
+-export([master_secret/4, client_hello/6, server_hello/5, hello/4,
hello_request/0, certify/7, certificate/4,
client_certificate_verify/5, certificate_verify/5,
certificate_request/3, key_exchange/2, server_key_exchange_hash/2,
@@ -69,22 +69,30 @@ client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions,
Id = ssl_manager:client_session_id(Host, Port, SslOpts, OwnCert),
+ %% Currently, the only server names supported are DNS hostnames -- RFC 6066
+ %% Section 3
+ SniHost = case inet_parse:domain(Host) of
+ false -> undefined;
+ true -> Host
+ end,
+
#client_hello{session_id = Id,
client_version = Version,
cipher_suites = cipher_suites(Ciphers, Renegotiation),
compression_methods = ssl_record:compressions(),
random = SecParams#security_parameters.client_random,
renegotiation_info =
- renegotiation_info(client, ConnectionStates, Renegotiation)
+ renegotiation_info(client, ConnectionStates, Renegotiation),
+ sni = SniHost
}.
%%--------------------------------------------------------------------
--spec server_hello(session_id(), tls_version(), #connection_states{},
- boolean()) -> #server_hello{}.
+-spec server_hello(session_id(), tls_version(), #connection_states{},
+ boolean(), inet:hostname()|undefined) -> #server_hello{}.
%%
%% Description: Creates a server hello message.
%%--------------------------------------------------------------------
-server_hello(SessionId, Version, ConnectionStates, Renegotiation) ->
+server_hello(SessionId, Version, ConnectionStates, Renegotiation, SNI) ->
Pending = ssl_record:pending_connection_state(ConnectionStates, read),
SecParams = Pending#connection_state.security_parameters,
#server_hello{server_version = Version,
@@ -94,7 +102,8 @@ server_hello(SessionId, Version, ConnectionStates, Renegotiation) ->
random = SecParams#security_parameters.server_random,
session_id = SessionId,
renegotiation_info =
- renegotiation_info(server, ConnectionStates, Renegotiation)
+ renegotiation_info(server, ConnectionStates, Renegotiation),
+ sni = SNI
}.
%%--------------------------------------------------------------------
@@ -140,7 +149,8 @@ hello(#server_hello{cipher_suite = CipherSuite, server_version = Version,
hello(#client_hello{client_version = ClientVersion, random = Random,
cipher_suites = CipherSuites,
- renegotiation_info = Info} = Hello,
+ renegotiation_info = Info,
+ sni = Host} = Hello,
#ssl_options{versions = Versions,
secure_renegotiate = SecureRenegotation} = SslOpts,
{Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) ->
@@ -149,8 +159,8 @@ hello(#client_hello{client_version = ClientVersion, random = Random,
true ->
{Type, #session{cipher_suite = CipherSuite,
compression_method = Compression} = Session}
- = select_session(Hello, Port, Session0, Version,
- SslOpts, Cache, CacheCb, Cert),
+ = select_session(Hello, Host, Port, Session0, Version,
+ SslOpts, Cache, CacheCb, Cert),
case CipherSuite of
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
@@ -584,10 +594,10 @@ path_validation_alert({bad_cert, unknown_ca}) ->
path_validation_alert(_) ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE).
-select_session(Hello, Port, Session, Version,
+select_session(Hello, Host, Port, Session, Version,
#ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb, Cert) ->
SuggestedSessionId = Hello#client_hello.session_id,
- SessionId = ssl_manager:server_session_id(Port, SuggestedSessionId,
+ SessionId = ssl_manager:server_session_id(Host, Port, SuggestedSessionId,
SslOpts, Cert),
Suites = available_suites(Cert, UserSuites, Version),
@@ -601,7 +611,7 @@ select_session(Hello, Port, Session, Version,
cipher_suite = CipherSuite,
compression_method = Compression}};
false ->
- {resumed, CacheCb:lookup(Cache, {Port, SessionId})}
+ {resumed, CacheCb:lookup(Cache, {{server, Host, Port}, SessionId})}
end.
available_suites(UserSuites, Version) ->
@@ -828,17 +838,24 @@ dec_hs(?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
?UINT16(Cs_length), CipherSuites:Cs_length/binary,
?BYTE(Cm_length), Comp_methods:Cm_length/binary,
- Extensions/binary>>) ->
-
- RenegotiationInfo = proplists:get_value(renegotiation_info, dec_hello_extensions(Extensions),
- undefined),
+ Extensions0/binary>>) ->
+
+ Extensions = dec_hello_extensions(Extensions0),
+ RenegotiationInfo = proplists:get_value(renegotiation_info, Extensions,
+ undefined),
+ SNIHostname = case proplists:get_value(sni, Extensions, undefined) of
+ undefined -> undefined;
+ SNI -> SNI#sni.hostname
+ end,
+
#client_hello{
client_version = {Major,Minor},
random = Random,
session_id = Session_ID,
cipher_suites = from_2bytes(CipherSuites),
compression_methods = Comp_methods,
- renegotiation_info = RenegotiationInfo
+ renegotiation_info = RenegotiationInfo,
+ sni = SNIHostname
};
dec_hs(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
@@ -855,10 +872,11 @@ dec_hs(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
dec_hs(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
Cipher_suite:2/binary, ?BYTE(Comp_method),
- ?UINT16(ExtLen), Extensions:ExtLen/binary>>) ->
-
- RenegotiationInfo = proplists:get_value(renegotiation_info, dec_hello_extensions(Extensions, []),
- undefined),
+ ?UINT16(ExtLen), Extensions0:ExtLen/binary>>) ->
+
+ Extensions = dec_hello_extensions(Extensions0, []),
+ RenegotiationInfo = proplists:get_value(renegotiation_info, Extensions,
+ undefined),
#server_hello{
server_version = {Major,Minor},
random = Random,
@@ -930,6 +948,17 @@ dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binar
dec_hello_extensions(Rest, [{renegotiation_info,
#renegotiation_info{renegotiated_connection = RenegotiateInfo}} | Acc]);
+dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(_ExtLength),
+ ?UINT16(_ServerNameLength), ?BYTE(_NameType),
+ ?UINT16(HostLen), Hostname:HostLen/binary, Rest/binary>>,
+ Acc) ->
+ TextHostname = binary_to_list(Hostname),
+ case inet_parse:domain(TextHostname) of
+ true -> dec_hello_extensions(Rest,
+ [{sni, #sni{hostname = TextHostname}} | Acc]);
+ false -> dec_hello_extensions(Rest, Acc)
+ end;
+
%% Ignore data following the ClientHello (i.e.,
%% extensions) if not understood.
dec_hello_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Acc) ->
@@ -971,13 +1000,15 @@ enc_hs(#client_hello{client_version = {Major, Minor},
session_id = SessionID,
cipher_suites = CipherSuites,
compression_methods = CompMethods,
- renegotiation_info = RenegotiationInfo}, _Version) ->
+ renegotiation_info = RenegotiationInfo,
+ sni = SNIHostname}, _Version) ->
SIDLength = byte_size(SessionID),
BinCompMethods = list_to_binary(CompMethods),
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
- Extensions = hello_extensions(RenegotiationInfo),
+ Extensions = hello_extensions([RenegotiationInfo,
+ #sni{hostname = SNIHostname}]),
ExtensionsBin = enc_hello_extensions(Extensions),
{?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
@@ -989,9 +1020,14 @@ enc_hs(#server_hello{server_version = {Major, Minor},
session_id = Session_ID,
cipher_suite = Cipher_suite,
compression_method = Comp_method,
- renegotiation_info = RenegotiationInfo}, _Version) ->
+ renegotiation_info = RenegotiationInfo,
+ sni = SNIHostname}, _Version) ->
SID_length = byte_size(Session_ID),
- Extensions = hello_extensions(RenegotiationInfo),
+ SNIExt = case SNIHostname of
+ undefined -> [];
+ _ -> [#sni{hostname = SNIHostname}]
+ end,
+ Extensions = hello_extensions([RenegotiationInfo] ++ SNIExt),
ExtensionsBin = enc_hello_extensions(Extensions),
{?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID/binary,
@@ -1044,11 +1080,17 @@ enc_bin_sig(BinSig) ->
Size = byte_size(BinSig),
<<?UINT16(Size), BinSig/binary>>.
-%% Renegotiation info, only current extension
-hello_extensions(#renegotiation_info{renegotiated_connection = undefined}) ->
- [];
-hello_extensions(#renegotiation_info{} = Info) ->
- [Info].
+hello_extensions(Extensions) -> hello_extensions(Extensions, []).
+
+hello_extensions([], Acc) -> Acc;
+hello_extensions([#renegotiation_info{renegotiated_connection = undefined} | Rest], Acc) ->
+ hello_extensions(Rest, Acc);
+hello_extensions([#renegotiation_info{} = Info | Rest], Acc) ->
+ hello_extensions(Rest, [Info | Acc]);
+hello_extensions([#sni{hostname = undefined} | Rest], Acc) ->
+ hello_extensions(Rest, Acc);
+hello_extensions([#sni{hostname = _Host} = SNI | Rest], Acc) ->
+ hello_extensions(Rest, [SNI | Acc]).
enc_hello_extensions(Extensions) ->
enc_hello_extensions(Extensions, <<>>).
@@ -1065,7 +1107,23 @@ enc_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = I
enc_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) ->
InfoLen = byte_size(Info),
Len = InfoLen +1,
- enc_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), ?BYTE(InfoLen), Info/binary, Acc/binary>>).
+ enc_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), ?BYTE(InfoLen), Info/binary, Acc/binary>>);
+
+enc_hello_extensions([#sni{hostname = undefined} | Rest], _Acc) ->
+ % Send empty SNI extension in server_hello
+ enc_hello_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(0)>>);
+enc_hello_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
+ HostLen = length(Hostname),
+ HostnameBin = list_to_binary(Hostname),
+ % Hostname type (1 byte) + Hostname length (2 bytes) + Hostname (HostLen bytes)
+ ServerNameLength = 1 + 2 + HostLen,
+ % NameTypeLen(1 byte) + (2 bytes) + HostLenSize(2 bytes) + HostLen
+ ExtLength = 1 + 2 + 2 + HostLen,
+ enc_hello_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(ExtLength),
+ ?UINT16(ServerNameLength),
+ ?BYTE(?SNI_NAMETYPE_HOST_NAME),
+ ?UINT16(HostLen), HostnameBin/binary,
+ Acc/binary>>).
from_3bytes(Bin3) ->
View
18 lib/ssl/src/ssl_handshake.hrl
@@ -89,7 +89,8 @@
session_id, % opaque SessionID<0..32>
cipher_suites, % cipher_suites<2..2^16-1>
compression_methods, % compression_methods<1..2^8-1>,
- renegotiation_info
+ renegotiation_info,
+ sni
}).
-record(server_hello, {
@@ -98,7 +99,8 @@
session_id, % opaque SessionID<0..32>
cipher_suite, % cipher_suites
compression_method, % compression_method
- renegotiation_info
+ renegotiation_info,
+ sni
}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -213,7 +215,15 @@
renegotiated_connection
}).
--endif. % -ifdef(ssl_handshake).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Server name indication RFC 6066 section 3
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-define(SNI_EXT, 16#0000).
+%% enum { host_name(0), (255) } NameType;
+-define(SNI_NAMETYPE_HOST_NAME, 0).
-
+-record(sni,{
+ hostname = undefined
+ }).
+-endif. % -ifdef(ssl_handshake).
View
3  lib/ssl/src/ssl_internal.hrl
@@ -106,7 +106,8 @@
% after which ssl_connection will
% go into hibernation
%% This option should only be set to true by inet_tls_dist
- erl_dist = false
+ erl_dist = false,
+ sni_hosts = []
}).
-record(socket_options,
View
81 lib/ssl/src/ssl_manager.erl
@@ -30,9 +30,8 @@
-export([start_link/1, start_link_dist/1,
connection_init/2, cache_pem_file/2,
lookup_trusted_cert/4,
- client_session_id/4, server_session_id/4,
- register_session/2, register_session/3, invalidate_session/2,
- invalidate_session/3]).
+ client_session_id/4, server_session_id/5,
+ register_session/4, invalidate_session/4]).
% Spawn export
-export([init_session_validator/1]).
@@ -123,38 +122,31 @@ client_session_id(Host, Port, SslOpts, OwnCert) ->
call({client_session_id, Host, Port, SslOpts, OwnCert}).
%%--------------------------------------------------------------------
--spec server_session_id(host(), inet:port_number(), #ssl_options{},
- der_cert()) -> session_id().
+-spec server_session_id(host(), inet:port_number(), session_id(),
+ #ssl_options{}, der_cert()) -> session_id().
%%
%% Description: Select a session id for the server.
%%--------------------------------------------------------------------
-server_session_id(Port, SuggestedSessionId, SslOpts, OwnCert) ->
- call({server_session_id, Port, SuggestedSessionId, SslOpts, OwnCert}).
+server_session_id(Host, Port, SuggestedSessionId, SslOpts, OwnCert) ->
+ call({server_session_id, Host, Port, SuggestedSessionId, SslOpts, OwnCert}).
%%--------------------------------------------------------------------
--spec register_session(inet:port_number(), #session{}) -> ok.
--spec register_session(host(), inet:port_number(), #session{}) -> ok.
+-spec register_session(atom(), host(), inet:port_number(), #session{}) -> ok.
%%
%% Description: Make the session available for reuse.
%%--------------------------------------------------------------------
-register_session(Host, Port, Session) ->
- cast({register_session, Host, Port, Session}).
+register_session(Role, Host, Port, Session) ->
+ cast({register_session, Role, Host, Port, Session}).
-register_session(Port, Session) ->
- cast({register_session, Port, Session}).
%%--------------------------------------------------------------------
--spec invalidate_session(inet:port_number(), #session{}) -> ok.
--spec invalidate_session(host(), inet:port_number(), #session{}) -> ok.
+-spec invalidate_session(atom(), host(), inet:port_number(), #session{}) -> ok.
%%
%% Description: Make the session unavailable for reuse. After
%% a the session has been marked "is_resumable = false" for some while
%% it will be safe to remove the data from the session database.
%%--------------------------------------------------------------------
-invalidate_session(Host, Port, Session) ->
- cast({invalidate_session, Host, Port, Session}).
-
-invalidate_session(Port, Session) ->
- cast({invalidate_session, Port, Session}).
+invalidate_session(Role, Host, Port, Session) ->
+ cast({invalidate_session, Role, Host, Port, Session}).
%%====================================================================
%% gen_server callbacks
@@ -221,11 +213,12 @@ handle_call({{client_session_id, Host, Port, SslOpts, OwnCert}, _}, _,
Id = ssl_session:id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
{reply, Id, State};
-handle_call({{server_session_id, Port, SuggestedSessionId, SslOpts, OwnCert}, _},
+handle_call({{server_session_id, Host, Port, SuggestedSessionId, SslOpts,
+ OwnCert}, _},
_, #state{session_cache_cb = CacheCb,
session_cache = Cache,
session_lifetime = LifeTime} = State) ->
- Id = ssl_session:id(Port, SuggestedSessionId, SslOpts,
+ Id = ssl_session:id(Host, Port, SuggestedSessionId, SslOpts,
Cache, CacheCb, LifeTime, OwnCert),
{reply, Id, State};
@@ -252,33 +245,20 @@ handle_call({{recache_pem, File, LastWrite}, Pid}, From,
%%
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast({register_session, Host, Port, Session},
+handle_cast({register_session, Role, Host, Port, Session},
#state{session_cache = Cache,
session_cache_cb = CacheCb} = State) ->
TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}),
NewSession = Session#session{time_stamp = TimeStamp},
- CacheCb:update(Cache, {{Host, Port},
+ CacheCb:update(Cache, {{Role, Host, Port},
NewSession#session.session_id}, NewSession),
{noreply, State};
-handle_cast({register_session, Port, Session},
- #state{session_cache = Cache,
- session_cache_cb = CacheCb} = State) ->
- TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}),
- NewSession = Session#session{time_stamp = TimeStamp},
- CacheCb:update(Cache, {Port, NewSession#session.session_id}, NewSession),
- {noreply, State};
-
-handle_cast({invalidate_session, Host, Port,
+handle_cast({invalidate_session, Role, Host, Port,
#session{session_id = ID} = Session},
#state{session_cache = Cache,
session_cache_cb = CacheCb} = State) ->
- invalidate_session(Cache, CacheCb, {{Host, Port}, ID}, Session, State);
-
-handle_cast({invalidate_session, Port, #session{session_id = ID} = Session},
- #state{session_cache = Cache,
- session_cache_cb = CacheCb} = State) ->
- invalidate_session(Cache, CacheCb, {Port, ID}, Session, State);
+ invalidate_session(Cache, CacheCb, {{Role, Host, Port}, ID}, Session, State);
handle_cast({recache_pem, File, LastWrite, Pid, From},
#state{certificate_db = [_, FileToRefDb, _]} = State0) ->
@@ -374,22 +354,14 @@ call(Msg) ->
cast(Msg) ->
gen_server:cast(get(ssl_manager), Msg).
-validate_session(Host, Port, Session, LifeTime) ->
+validate_session(Role, Host, Port, Session, LifeTime) ->
case ssl_session:valid_session(Session, LifeTime) of
true ->
ok;
false ->
- invalidate_session(Host, Port, Session)
+ invalidate_session(Role, Host, Port, Session)
end.
-validate_session(Port, Session, LifeTime) ->
- case ssl_session:valid_session(Session, LifeTime) of
- true ->
- ok;
- false ->
- invalidate_session(Port, Session)
- end.
-
start_session_validator(Cache, CacheCb, LifeTime) ->
spawn_link(?MODULE, init_session_validator,
[[get(ssl_manager), Cache, CacheCb, LifeTime]]).
@@ -399,11 +371,8 @@ init_session_validator([SslManagerName, Cache, CacheCb, LifeTime]) ->
CacheCb:foldl(fun session_validation/2,
LifeTime, Cache).
-session_validation({{{Host, Port}, _}, Session}, LifeTime) ->
- validate_session(Host, Port, Session, LifeTime),
- LifeTime;
-session_validation({{Port, _}, Session}, LifeTime) ->
- validate_session(Port, Session, LifeTime),
+session_validation({{{Role, Host, Port}, _}, Session}, LifeTime) ->
+ validate_session(Role, Host, Port, Session, LifeTime),
LifeTime.
cache_pem_file(File, LastWrite, DbHandle) ->
@@ -444,7 +413,7 @@ invalidate_session(Cache, CacheCb, Key, Session, #state{last_delay_timer = LastT
{noreply, State#state{last_delay_timer = last_delay_timer(Key, TRef, LastTimer)}}
end.
-last_delay_timer({{_,_},_}, TRef, {LastServer, _}) ->
+last_delay_timer({{client,_,_},_}, TRef, {LastServer, _}) ->
{LastServer, TRef};
-last_delay_timer({_,_}, TRef, {_, LastClient}) ->
+last_delay_timer({{server,_,_},_}, TRef, {_, LastClient}) ->
{TRef, LastClient}.
View
34 lib/ssl/src/ssl_session.erl
@@ -28,7 +28,7 @@
-include("ssl_internal.hrl").
%% Internal application API
--export([is_new/2, id/4, id/7, valid_session/2]).
+-export([is_new/2, id/4, id/8, valid_session/2]).
-define(GEN_UNIQUE_ID_MAX_TRIES, 10).
@@ -63,24 +63,24 @@ id(ClientInfo, Cache, CacheCb, OwnCert) ->
end.
%%--------------------------------------------------------------------
--spec id(inet:port_number(), binary(), #ssl_options{}, db_handle(),
+-spec id(host(), inet:port_number(), binary(), #ssl_options{}, db_handle(),
atom(), seconds(), binary()) -> binary().
%%
%% Description: Should be called by the server side to get an id
%% for the server hello message.
%%--------------------------------------------------------------------
-id(Port, <<>>, _, Cache, CacheCb, _, _) ->
- new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb);
+id(Host, Port, <<>>, _, Cache, CacheCb, _, _) ->
+ new_id(Host, Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb);
-id(Port, SuggestedSessionId, #ssl_options{reuse_sessions = ReuseEnabled,
+id(Host, Port, SuggestedSessionId, #ssl_options{reuse_sessions = ReuseEnabled,
reuse_session = ReuseFun},
Cache, CacheCb, SecondLifeTime, OwnCert) ->
- case is_resumable(SuggestedSessionId, Port, ReuseEnabled,
+ case is_resumable(SuggestedSessionId, Host, Port, ReuseEnabled,
ReuseFun, Cache, CacheCb, SecondLifeTime, OwnCert) of
true ->
SuggestedSessionId;
false ->
- new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb)
+ new_id(Host, Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb)
end.
%%--------------------------------------------------------------------
-spec valid_session(#session{}, seconds()) -> boolean().
@@ -95,7 +95,7 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) ->
%%% Internal functions
%%--------------------------------------------------------------------
select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) ->
- Sessions = CacheCb:select_session(Cache, {HostIP, Port}),
+ Sessions = CacheCb:select_session(Cache, {client, HostIP, Port}),
select_session(Sessions, SslOpts, OwnCert).
select_session([], _, _) ->
@@ -123,27 +123,27 @@ select_session(Sessions, #ssl_options{ciphers = Ciphers,
%% ?GEN_UNIQUE_ID_MAX_TRIES either the RAND code is broken or someone
%% is trying to open roughly very close to 2^128 (or 2^256) SSL
%% sessions to our server"
-new_id(_, 0, _, _) ->
+new_id(_, _, 0, _, _) ->
<<>>;
-new_id(Port, Tries, Cache, CacheCb) ->
+new_id(Host, Port, Tries, Cache, CacheCb) ->
Id = crypto:rand_bytes(?NUM_OF_SESSION_ID_BYTES),
- case CacheCb:lookup(Cache, {Port, Id}) of
+ case CacheCb:lookup(Cache, {{server, Host, Port}, Id}) of
undefined ->
Now = calendar:datetime_to_gregorian_seconds({date(), time()}),
%% New sessions can not be set to resumable
%% until handshake is compleate and the
%% other session values are set.
- CacheCb:update(Cache, {Port, Id}, #session{session_id = Id,
- is_resumable = false,
- time_stamp = Now}),
+ CacheCb:update(Cache, {{server, Host, Port}, Id},
+ #session{session_id = Id, is_resumable = false,
+ time_stamp = Now}),
Id;
_ ->
- new_id(Port, Tries - 1, Cache, CacheCb)
+ new_id(Host, Port, Tries - 1, Cache, CacheCb)
end.
-is_resumable(SuggestedSessionId, Port, ReuseEnabled, ReuseFun, Cache,
+is_resumable(SuggestedSessionId, Host, Port, ReuseEnabled, ReuseFun, Cache,
CacheCb, SecondLifeTime, OwnCert) ->
- case CacheCb:lookup(Cache, {Port, SuggestedSessionId}) of
+ case CacheCb:lookup(Cache, {{server, Host, Port}, SuggestedSessionId}) of
#session{cipher_suite = CipherSuite,
own_certificate = SessionOwnCert,
compression_method = Compression,
View
63 lib/ssl/test/ssl_basic_SUITE.erl
@@ -208,6 +208,7 @@ all() ->
[app, alerts, connection_info, protocol_versions,
empty_protocol_versions, controlling_process,
controller_dies, client_closes_socket,
+ sni_server_default, sni_server_select,
connect_dist, peername, peercert, sockname, socket_options,
invalid_inet_get_option, invalid_inet_get_option_not_list,
invalid_inet_get_option_improper_list,
@@ -614,8 +615,66 @@ client_closes_socket(Config) when is_list(Config) ->
ssl_test_lib:check_result(Server, {error,closed}).
%%--------------------------------------------------------------------
-connect_dist(doc) ->
- ["Test a simple connect as is used by distribution"];
+sni_server_default(doc) ->
+ ["Check that a server select default server (undefined) when SNI hostname is
+ not recognized"];
+sni_server_default(suite) -> [];
+sni_server_default(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, sni_hostname_result, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, ClientOpts}]),
+
+ SelectedSNI = undefined,
+ ssl_test_lib:check_result(Server, SelectedSNI),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+sni_hostname_result(Socket) ->
+ ssl:sni_hostname(Socket).
+
+%%--------------------------------------------------------------------
+sni_server_select(doc) ->
+ ["Check that a server select the good server configuration when asked by
+ client"];
+sni_server_select(suite) -> [];
+sni_server_select(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ SNIOpts = {sni_hosts, [{Hostname, [{verify, verify_peer},
+ {fail_if_no_peer_cert, true}]}]},
+ Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, [SNIOpts | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client_error(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options,
+ [proplists:delete(certfile, ClientOpts)]}]),
+
+ ssl_test_lib:check_result(Server, {error, esslaccept}, Client, {error, esslconnect}),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+connect_dist(doc) -> ["Test a simple connect as is used by distribution"];
connect_dist(suite) ->
[];
View
8 lib/ssl/test/ssl_handshake_SUITE.erl
@@ -32,6 +32,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() -> [
decode_hello_handshake,
decode_single_hello_extension_correctly,
+ decode_single_hello_extension_sni_correctly,
decode_unknown_hello_extension_correctly].
decode_hello_handshake(_Config) ->
@@ -57,7 +58,12 @@ decode_single_hello_extension_correctly(_Config) ->
Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>,
Extensions = ssl_handshake:dec_hello_extensions(Renegotiation, []),
[{renegotiation_info,#renegotiation_info{renegotiated_connection = <<0>>}}] = Extensions.
-
+
+decode_single_hello_extension_sni_correctly(_Config) ->
+ SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08,
+ $t, $e, $s, $t, $., $c, $o, $m>>,
+ Extensions = ssl_handshake:dec_hello_extensions(SNI, []),
+ [{sni, #sni{hostname = "test.com"}}] = Extensions.
decode_unknown_hello_extension_correctly(_Config) ->
FourByteUnknown = <<16#CA,16#FE, ?UINT16(4), 3, 0, 1, 2>>,
View
8 lib/ssl/test/ssl_session_cache_SUITE.erl
@@ -215,8 +215,8 @@ session_cleanup(Config)when is_list(Config) ->
SessionTimer = element(6, State),
Id = proplists:get_value(session_id, SessionInfo),
- CSession = ssl_session_cache:lookup(Cache, {{Hostname, Port}, Id}),
- SSession = ssl_session_cache:lookup(Cache, {Port, Id}),
+ CSession = ssl_session_cache:lookup(Cache, {{client, Hostname, Port}, Id}),
+ SSession = ssl_session_cache:lookup(Cache, {{server, undefined, Port}, Id}),
true = CSession =/= undefined,
true = SSession =/= undefined,
@@ -232,8 +232,8 @@ session_cleanup(Config)when is_list(Config) ->
test_server:sleep(?SLEEP), %% Make sure clean has had time to run
- undefined = ssl_session_cache:lookup(Cache, {{Hostname, Port}, Id}),
- undefined = ssl_session_cache:lookup(Cache, {Port, Id}),
+ undefined = ssl_session_cache:lookup(Cache, {{client, Hostname, Port}, Id}),
+ undefined = ssl_session_cache:lookup(Cache, {{server, undefined, Port}, Id}),
process_flag(trap_exit, false),
ssl_test_lib:close(Server),

Showing you all comments on commits in this comparison.

@RoadRunnr

This begs for a larger code re-factoring which should definitely be discussed with Ingela Andin first.

All the public keys and certificate chains for the primary and additional hosts should be loaded in by the main ssl code, hello would then just select them. Also ssl_init currently generates dh key pairs, which can take quite some time and is completely wasted when we select a ecdh cipher suite. So this work should be move to hello after the host and cipher have been decided.

Also of this is not directly SNI related, but would make the code here much simple and straight forward

Something went wrong with that request. Please try again.