From bc8b6bf58c96f8d5a07146ddea145f71fe8c8956 Mon Sep 17 00:00:00 2001
From: Julien Barbot <julien@barbot.org>
Date: Tue, 29 Oct 2013 22:29:01 +0100
Subject: Add SSL Server Name Indication (SNI) client support

See RFC 6066 section 3
---
 lib/ssl/doc/src/ssl.xml              |  2 ++
 lib/ssl/src/dtls_handshake.erl       |  2 +-
 lib/ssl/src/ssl_handshake.erl        | 33 +++++++++++++++++++++++++++------
 lib/ssl/src/ssl_handshake.hrl        | 19 +++++++++++++++----
 lib/ssl/src/tls_handshake.erl        |  2 +-
 lib/ssl/test/ssl_handshake_SUITE.erl | 12 +++++++++++-
 6 files changed, 57 insertions(+), 13 deletions(-)

(limited to 'lib/ssl')

diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 445a47c07b..aac04095b4 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -52,6 +52,8 @@
       <item>CRL and policy certificate extensions are not supported
       yet. However CRL verification is supported by public_key, only not integrated
       in ssl yet. </item>
+      <item>Support for 'Server Name Indication' extension client side
+      (RFC 6066 section 3).</item>
     </list>
  
   </section>
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 26e8ce7503..d0f9649f9f 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -46,7 +46,7 @@ client_hello(Host, Port, Cookie, ConnectionStates,
     SecParams = Pending#connection_state.security_parameters,
     CipherSuites = ssl_handshake:available_suites(UserSuites, Version),
 
-    Extensions = ssl_handshake:client_hello_extensions(Version, CipherSuites,
+    Extensions = ssl_handshake:client_hello_extensions(Host, Version, CipherSuites,
 						SslOpts, ConnectionStates, Renegotiation),
 
     Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index b18452a8f2..e1fd6970cc 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -53,7 +53,7 @@
 	 select_session/10, supported_ecc/1]).
 
 %% Extensions handling
--export([client_hello_extensions/5,
+-export([client_hello_extensions/6,
 	 handle_client_hello_extensions/8, %% Returns server hello extensions
 	 handle_server_hello_extensions/9, select_curve/2
 	]).
@@ -85,7 +85,7 @@ hello_request() ->
 server_hello_done() ->
     #server_hello_done{}.
 
-client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) ->
+client_hello_extensions(Host, Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) ->
     {EcPointFormats, EllipticCurves} =
 	case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of
 	    true ->
@@ -104,7 +104,8 @@ client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renego
        elliptic_curves = EllipticCurves,
        next_protocol_negotiation =
 	   encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector,
-					      Renegotiation)}.
+					      Renegotiation),
+       sni = sni(Host)}.
 
 %%--------------------------------------------------------------------
 -spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}.
@@ -641,7 +642,19 @@ encode_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Res
     ListLen = byte_size(SignAlgoList),
     Len = ListLen + 2,
     encode_hello_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT),
-				 ?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>).
+				 ?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>);
+encode_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,
+    % ServerNameListSize (2 bytes) + ServerNameLength
+    ExtLength = 2 + ServerNameLength,
+    encode_hello_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(ExtLength),
+				    ?UINT16(ServerNameLength),
+				    ?BYTE(?SNI_NAMETYPE_HOST_NAME),
+				    ?UINT16(HostLen), HostnameBin/binary,
+				    Acc/binary>>).
 
 enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo},
 			ClientRandom, ServerRandom, PrivateKey) ->
@@ -1081,9 +1094,10 @@ hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo,
 					hash_signs = HashSigns,
 					ec_point_formats = EcPointFormats,
 					elliptic_curves = EllipticCurves,
-					next_protocol_negotiation = NextProtocolNegotiation}) ->
+					next_protocol_negotiation = NextProtocolNegotiation,
+					sni = Sni}) ->
     [Ext || Ext <- [RenegotiationInfo, SRP, HashSigns,
-		    EcPointFormats,EllipticCurves, NextProtocolNegotiation], Ext =/= undefined].
+		    EcPointFormats, EllipticCurves, NextProtocolNegotiation, Sni], Ext =/= undefined].
 
 srp_user(#ssl_options{srp_identity = {UserName, _}}) ->
     #srp{username = UserName};
@@ -1146,6 +1160,13 @@ select_curve(Curves, [Curve| Rest]) ->
 	    select_curve(Curves, Rest)
     end.
 
+sni(Host) ->
+    %% RFC 6066, Section 3: Currently, the only server names supported are
+    %% DNS hostnames
+    case inet_parse:domain(Host) of
+        false -> undefined;
+        true -> #sni{hostname = Host}
+    end.
 %%--------------------------------------------------------------------
 %%% Internal functions
 %%--------------------------------------------------------------------
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index f25b0df806..75160526b9 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -98,7 +98,8 @@
 	  next_protocol_negotiation = undefined, % [binary()]
 	  srp,
 	  ec_point_formats,
-	  elliptic_curves
+	  elliptic_curves,
+	  sni
 	 }).
 
 -record(server_hello, {
@@ -338,6 +339,19 @@
 -define(EXPLICIT_CHAR2, 2).
 -define(NAMED_CURVE, 3).
 
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% 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
+        }).
+
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% Dialyzer types
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -353,6 +367,3 @@
 
 
 -endif. % -ifdef(ssl_handshake).
-
-
-     
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index ecbca83e10..262f2d929f 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -56,7 +56,7 @@ client_hello(Host, Port, ConnectionStates,
     SecParams = Pending#connection_state.security_parameters,
     CipherSuites = ssl_handshake:available_suites(UserSuites, Version),
 
-    Extensions = ssl_handshake:client_hello_extensions(Version, CipherSuites,
+    Extensions = ssl_handshake:client_hello_extensions(Host, Version, CipherSuites,
 						SslOpts, ConnectionStates, Renegotiation),
 
     Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl
index 9695710230..7e8e8d2611 100644
--- a/lib/ssl/test/ssl_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_handshake_SUITE.erl
@@ -34,7 +34,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
 
 all() -> [decode_hello_handshake,
 	  decode_single_hello_extension_correctly,
-	  decode_unknown_hello_extension_correctly].
+	  decode_unknown_hello_extension_correctly,
+	  encode_single_hello_sni_extension_correctly].
 
 %%--------------------------------------------------------------------
 %% Test Cases --------------------------------------------------------
@@ -73,3 +74,12 @@ decode_unknown_hello_extension_correctly(_Config) ->
     Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>),
      #renegotiation_info{renegotiated_connection = <<0>>}
 	= Extensions#hello_extensions.renegotiation_info.
+
+encode_single_hello_sni_extension_correctly(_Config) ->
+    Exts = #hello_extensions{sni = #sni{hostname = "test.com"}},
+    SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08,
+	    $t,    $e,    $s,    $t,    $.,    $c,    $o,    $m>>,
+    ExtSize = byte_size(SNI),
+    HelloExt = <<ExtSize:16/unsigned-big-integer, SNI/binary>>,
+    Encoded = ssl_handshake:encode_hello_extensions(Exts),
+    HelloExt = Encoded.
-- 
cgit v1.2.3


From d370fe05f5884691a89784aa73bfb4eb2176edab Mon Sep 17 00:00:00 2001
From: Julien Barbot <julien@barbot.org>
Date: Sun, 3 Nov 2013 21:30:03 +0100
Subject: Add a new server_name_indication option to ssl:connect

- Set to disable to explicitly disable SNI support.
- Set to a hostname when upgrading from TCP to TLS.
---
 lib/ssl/doc/src/ssl.xml       | 10 +++++++++-
 lib/ssl/src/ssl_handshake.erl | 18 ++++++++++++------
 lib/ssl/src/ssl_internal.hrl  |  3 ++-
 lib/ssl/src/tls.erl           |  9 ++++++++-
 4 files changed, 31 insertions(+), 9 deletions(-)

(limited to 'lib/ssl')

diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index aac04095b4..b4182e6d61 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -89,7 +89,7 @@
       {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()}
       {next_protocols_advertised, [binary()]} |
       {client_preferred_next_protocols, {client | server, [binary()]} | {client | server, [binary()], binary()}} |
-      {log_alert, boolean()}
+      {log_alert, boolean()} | {server_name_indication, hostname() | disable}
     </c></p>
 
     <p><c>transportoption() = {cb_info, {CallbackModule::atom(), DataTag::atom(), ClosedTag::atom(), ErrTag:atom()}}
@@ -384,6 +384,14 @@ fun(srp, Username :: string(), UserState :: term()) ->
       <tag>{srp_identity, {Username :: string(), Password :: string()}</tag>
       <item>Specifies the Username and Password to use to authenticate to the server.
       </item>
+      <tag>{server_name_indication, hostname()}</tag>
+      <tag>{server_name_indication, disable}</tag>
+      <item>
+        <p>This option can be specified when upgrading a tcp socket to a tls
+        socket to use the TLS Server Name Indication extension.</p>
+        <p>This option can also be set to disable to explicitly disable usage of
+        the Server Name Indication extension.</p>
+      </item>
     </taglist>
    </section>
 
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index e1fd6970cc..9142a260b1 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -105,7 +105,7 @@ client_hello_extensions(Host, Version, CipherSuites, SslOpts, ConnectionStates,
        next_protocol_negotiation =
 	   encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector,
 					      Renegotiation),
-       sni = sni(Host)}.
+       sni = sni(Host, SslOpts#ssl_options.server_name_indication)}.
 
 %%--------------------------------------------------------------------
 -spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}.
@@ -1159,13 +1159,19 @@ select_curve(Curves, [Curve| Rest]) ->
 	false ->
 	    select_curve(Curves, Rest)
     end.
+%% RFC 6066, Section 3: Currently, the only server names supported are
+%% DNS hostnames
+sni(_, disable) ->
+    undefined;
+sni(Host, undefined) ->
+    sni1(Host);
+sni(_Host, SNIOption) ->
+    sni1(SNIOption).
 
-sni(Host) ->
-    %% RFC 6066, Section 3: Currently, the only server names supported are
-    %% DNS hostnames
-    case inet_parse:domain(Host) of
+sni1(Hostname) ->
+    case inet_parse:domain(Hostname) of
         false -> undefined;
-        true -> #sni{hostname = Host}
+        true -> #sni{hostname = Hostname}
     end.
 %%--------------------------------------------------------------------
 %%% Internal functions
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 96e3280fb5..a582b8c290 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -115,7 +115,8 @@
 	  erl_dist = false,
 	  next_protocols_advertised = undefined, %% [binary()],
 	  next_protocol_selector = undefined,  %% fun([binary()]) -> binary())
-	  log_alert
+	  log_alert,
+	  server_name_indication = undefined
 	  }).
 
 -record(socket_options,
diff --git a/lib/ssl/src/tls.erl b/lib/ssl/src/tls.erl
index b220a48f73..f1747dc69e 100644
--- a/lib/ssl/src/tls.erl
+++ b/lib/ssl/src/tls.erl
@@ -664,7 +664,8 @@ handle_options(Opts0, _Role) ->
       next_protocol_selector = 
 			make_next_protocol_selector(
 			  handle_option(client_preferred_next_protocols, Opts, undefined)),
-      log_alert = handle_option(log_alert, Opts, true)
+      log_alert = handle_option(log_alert, Opts, true),
+      server_name_indication = handle_option(server_name_indication, Opts, undefined)
      },
 
     CbInfo  = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),    
@@ -855,6 +856,12 @@ validate_option(next_protocols_advertised = Opt, Value) when is_list(Value) ->
 
 validate_option(next_protocols_advertised, undefined) ->
     undefined;
+validate_option(server_name_indication, Value) when is_list(Value) ->
+    Value;
+validate_option(server_name_indication, disable) ->
+    disable;
+validate_option(server_name_indication, undefined) ->
+    undefined;
 validate_option(Opt, Value) ->
     throw({error, {options, {Opt, Value}}}).
 
-- 
cgit v1.2.3


From 06d4f009136b853cd8b50a6b5e8ae0ff5bb54041 Mon Sep 17 00:00:00 2001
From: Julien Barbot <julien@barbot.org>
Date: Mon, 4 Nov 2013 10:19:04 +0100
Subject: Update documentation

---
 lib/ssl/doc/src/ssl.xml | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

(limited to 'lib/ssl')

diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index b4182e6d61..19c0c8c9ee 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -387,10 +387,11 @@ fun(srp, Username :: string(), UserState :: term()) ->
       <tag>{server_name_indication, hostname()}</tag>
       <tag>{server_name_indication, disable}</tag>
       <item>
-        <p>This option can be specified when upgrading a tcp socket to a tls
+        <p>This option can be specified when upgrading a TCP socket to a TLS
         socket to use the TLS Server Name Indication extension.</p>
-        <p>This option can also be set to disable to explicitly disable usage of
-        the Server Name Indication extension.</p>
+        <p>When starting a TLS connection without upgrade the Server Name
+        Indication extension will be sent if possible, this option may also be
+        used to disable that behavior.</p>
       </item>
     </taglist>
    </section>
-- 
cgit v1.2.3