aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl')
-rw-r--r--lib/ssl/doc/src/notes.xml17
-rw-r--r--lib/ssl/doc/src/ssl.xml52
-rw-r--r--lib/ssl/src/Makefile20
-rw-r--r--lib/ssl/src/ssl.appup.src4
-rw-r--r--lib/ssl/src/ssl.erl199
-rw-r--r--lib/ssl/src/ssl_connection.erl284
-rw-r--r--lib/ssl/src/ssl_handshake.erl212
-rw-r--r--lib/ssl/src/ssl_handshake.hrl20
-rw-r--r--lib/ssl/src/ssl_internal.hrl4
-rw-r--r--lib/ssl/src/ssl_manager.erl2
-rw-r--r--lib/ssl/src/ssl_session.erl18
-rw-r--r--lib/ssl/test/Makefile2
-rw-r--r--lib/ssl/test/erl_make_certs.erl4
-rw-r--r--lib/ssl/test/make_certs.erl19
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl221
-rw-r--r--lib/ssl/test/ssl_npn_handshake_SUITE.erl310
-rw-r--r--lib/ssl/test/ssl_npn_hello_SUITE.erl117
-rw-r--r--lib/ssl/test/ssl_test_lib.erl27
-rw-r--r--lib/ssl/test/ssl_to_openssl_SUITE.erl335
-rw-r--r--lib/ssl/vsn.mk2
20 files changed, 1613 insertions, 256 deletions
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 7e751a215d..49bbd5d27d 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -30,7 +30,22 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
- <section><title>SSL 5.1.1</title>
+ <section><title>SSL 5.1.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ ssl:ssl_accept/2 timeout is no longer ignored</p>
+ <p>
+ Own Id: OTP-10600</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 5.1.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 5098d26a3a..f0eac76264 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -79,7 +79,9 @@
{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()}
+ {next_protocols_advertised, list(binary()} |
+ {client_preferred_next_protocols, binary(), client | server, list(binary())}
</c></p>
<p><c>transportoption() = {CallbackModule, DataTag, ClosedTag}
@@ -301,8 +303,29 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} |
when possible.
</item>
+ <tag>{client_preferred_next_protocols, Precedence:: server | client, ClientPrefs::[binary()]}
+ {client_preferred_next_protocols, Precedence:: server | client, ClientPrefs::[binary()] , Default :: binary()}}</tag>
+
+ <item> <p>Indicates the client will try to perform Next Protocol
+ Negotiation.</p>
+
+ <p>If precedence is server the negaotiated protocol will be the
+ first protocol that appears on the server advertised list that is
+ also on the clients preference list.</p>
+
+ <p>If the precedence is client the negaotiated protocol will be the
+ first protocol that appears on the clients preference list that is
+ also on the server advertised list.</p>
+
+ <p> If the client does not support any of the servers advertised
+ protocols or the server does not advertise any protocols the
+ client will fallback to the first protocol in its list or if a
+ default is supplied it will fallback to that instead. If the
+ server does not support next protocol renegotiation the
+ connection will be aborted if no default protocol is supplied.</p>
+ </item>
</taglist>
- </section>
+ </section>
<section>
<title>SSL OPTION DESCRIPTIONS - SERVER SIDE</title>
@@ -353,6 +376,14 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} |
SuggestedSessionId is a binary(), PeerCert is a DER encoded
certificate, Compression is an enumeration integer
and CipherSuite is of type ciphersuite().
+ </item>
+
+ <tag>{next_protocols_advertised, Protocols :: list(binary())}</tag>
+ <item>The list of protocols to send to the client if the client indicates
+ it supports the Next Protocol extension. The client may select a protocol
+ that is not on this list. The list of protocols must not contain an empty
+ binary. If the server negotiates a Next Protocol it can be accessed
+ using <c>negotiated_next_protocol/1</c> method.
</item>
</taglist>
@@ -766,8 +797,23 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} |
ssl application.</p>
</desc>
</func>
+ <func>
+ <name>negotiated_next_protocol(Socket) -> {ok, Protocol} | {error, next_protocol_not_negotiated}</name>
+ <fsummary>Returns the Next Protocol negotiated.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>Protocol = binary()</v>
+ </type>
+ <desc>
+ <p>
+ Returns the Next Protocol negotiated.
+ </p>
+ </desc>
+ </func>
+
+
</funcs>
-
+
<section>
<title>SEE ALSO</title>
<p><seealso marker="kernel:inet">inet(3) </seealso> and
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index c5c5bf593a..6be8a1456e 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -130,3 +130,23 @@ release_spec: opt
release_docs_spec:
+# ----------------------------------------------------
+# Dependencies
+# ----------------------------------------------------
+$(EBIN)/inet_tls_dist.$(EMULATOR): ../../kernel/include/net_address.hrl ../../kernel/include/dist.hrl ../../kernel/include/dist_util.hrl
+$(EBIN)/ssl.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ../../public_key/include/public_key.hrl
+$(EBIN)/ssl_alert.$(EMULATOR): ssl_alert.hrl ssl_record.hrl
+$(EBIN)/ssl_certificate.$(EMULATOR): ssl_internal.hrl ssl_alert.hrl ssl_handshake.hrl ../../public_key/include/public_key.hrl
+$(EBIN)/ssl_certificate_db.$(EMULATOR): ssl_internal.hrl ../../public_key/include/public_key.hrl ../../kernel/include/file.hrl
+$(EBIN)/ssl_cipher.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
+$(EBIN)/ssl_connection.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
+$(EBIN)/ssl_handshake.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
+$(EBIN)/ssl_manager.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl ../../kernel/include/file.hrl
+$(EBIN)/ssl_record.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl
+$(EBIN)/ssl_session.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl
+$(EBIN)/ssl_session_cache.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl
+$(EBIN)/ssl_session_cache_api.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl
+$(EBIN)/ssl_ssl3.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl
+$(EBIN)/ssl_tls1.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl
+
+
diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src
index c118c129e8..9b1227fa7f 100644
--- a/lib/ssl/src/ssl.appup.src
+++ b/lib/ssl/src/ssl.appup.src
@@ -1,6 +1,8 @@
%% -*- erlang -*-
{"%VSN%",
[
+ {"5.1.1", [{restart_application, ssl}]
+ },
{"5.1", [
{load_module, ssl_connection, soft_purge, soft_purge, []}
]
@@ -10,6 +12,8 @@
{<<"3\\.*">>, [{restart_application, ssl}]}
],
[
+ {"5.1.1", [{restart_application, ssl}]
+ },
{"5.1", [
{load_module, ssl_connection, soft_purge, soft_purge, []}
]
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 69e8d868fa..6224334a6e 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -31,13 +31,15 @@
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, clear_pem_cache/0, random_bytes/1]).
+ renegotiate/1, prf/5, clear_pem_cache/0, random_bytes/1, negotiated_next_protocol/1]).
+
-deprecated({pid, 1, next_major_release}).
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
-include("ssl_cipher.hrl").
+-include("ssl_handshake.hrl").
-include_lib("public_key/include/public_key.hrl").
@@ -45,7 +47,7 @@
-export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0,
erl_cipher_suite/0, %% From ssl_cipher.hrl
tls_atom_version/0, %% From ssl_internal.hrl
- prf_random/0]).
+ prf_random/0, sslsocket/0]).
-record(config, {ssl, %% SSL parameters
inet_user, %% User set inet options
@@ -53,6 +55,8 @@
inet_ssl, %% inet options for internal ssl socket
cb %% Callback info
}).
+
+-type sslsocket() :: #sslsocket{}.
-type connect_option() :: socket_connect_option() | ssl_option() | transport_option().
-type socket_connect_option() :: gen_tcp:connect_option().
-type listen_option() :: socket_listen_option() | ssl_option() | transport_option().
@@ -65,7 +69,9 @@
{keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} |
{cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} |
{ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} |
- {reuse_session, fun()} | {hibernate_after, integer()|undefined}.
+ {reuse_session, fun()} | {hibernate_after, integer()|undefined} |
+ {next_protocols_advertised, list(binary())} |
+ {client_preferred_next_protocols, binary(), client | server, list(binary())}.
-type verify_type() :: verify_none | verify_peer.
-type path() :: string().
@@ -161,7 +167,7 @@ listen(Port, Options0) ->
#config{cb={CbModule, _, _, _},inet_user=Options} = Config,
case CbModule:listen(Port, Options) of
{ok, ListenSocket} ->
- {ok, #sslsocket{pid = {ListenSocket, Config}, fd = new_ssl}};
+ {ok, #sslsocket{pid = {ListenSocket, Config}}};
Err = {error, _} ->
Err
end
@@ -241,18 +247,20 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
%%
%% Description: Close an ssl connection
%%--------------------------------------------------------------------
+close(#sslsocket{pid = Pid}) when is_pid(Pid) ->
+ ssl_connection:close(Pid);
close(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _, _}}}}) ->
- CbMod:close(ListenSocket);
-close(#sslsocket{pid = Pid}) ->
- ssl_connection:close(Pid).
+ CbMod:close(ListenSocket).
%%--------------------------------------------------------------------
-spec send(#sslsocket{}, iodata()) -> ok | {error, reason()}.
%%
%% Description: Sends data over the ssl connection
%%--------------------------------------------------------------------
-send(#sslsocket{pid = Pid}, Data) ->
- ssl_connection:send(Pid, Data).
+send(#sslsocket{pid = Pid}, Data) when is_pid(Pid) ->
+ ssl_connection:send(Pid, Data);
+send(#sslsocket{pid = {ListenSocket, #config{cb={CbModule, _, _, _}}}}, Data) ->
+ CbModule:send(ListenSocket, Data). %% {error,enotconn}
%%--------------------------------------------------------------------
-spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}.
@@ -262,8 +270,10 @@ send(#sslsocket{pid = Pid}, Data) ->
%%--------------------------------------------------------------------
recv(Socket, Length) ->
recv(Socket, Length, infinity).
-recv(#sslsocket{pid = Pid, fd = new_ssl}, Length, Timeout) ->
- ssl_connection:recv(Pid, Length, Timeout).
+recv(#sslsocket{pid = Pid}, Length, Timeout) when is_pid(Pid) ->
+ ssl_connection:recv(Pid, Length, Timeout);
+recv(#sslsocket{pid = {Listen, #config{cb={CbModule, _, _, _}}}}, _,_) when is_port(Listen)->
+ CbModule:recv(Listen, 0). %% {error,enotconn}
%%--------------------------------------------------------------------
-spec controlling_process(#sslsocket{}, pid()) -> ok | {error, reason()}.
@@ -271,8 +281,12 @@ recv(#sslsocket{pid = Pid, fd = new_ssl}, Length, Timeout) ->
%% Description: Changes process that receives the messages when active = true
%% or once.
%%--------------------------------------------------------------------
-controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid) ->
- ssl_connection:new_user(Pid, NewOwner).
+controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid), is_pid(NewOwner) ->
+ ssl_connection:new_user(Pid, NewOwner);
+controlling_process(#sslsocket{pid = {Listen,
+ #config{cb={CbModule, _, _, _}}}}, NewOwner) when is_port(Listen),
+ is_pid(NewOwner) ->
+ CbModule:controlling_process(Listen, NewOwner).
%%--------------------------------------------------------------------
-spec connection_info(#sslsocket{}) -> {ok, {tls_atom_version(), erl_cipher_suite()}} |
@@ -280,29 +294,35 @@ controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid) ->
%%
%% Description: Returns ssl protocol and cipher used for the connection
%%--------------------------------------------------------------------
-connection_info(#sslsocket{pid = Pid}) ->
- ssl_connection:info(Pid).
+connection_info(#sslsocket{pid = Pid}) when is_pid(Pid) ->
+ ssl_connection:info(Pid);
+connection_info(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
+ {error, enotconn}.
%%--------------------------------------------------------------------
-spec peername(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
%%
%% Description: same as inet:peername/1.
%%--------------------------------------------------------------------
-peername(#sslsocket{pid = Pid}) ->
- ssl_connection:peername(Pid).
+peername(#sslsocket{pid = Pid, fd = Socket}) when is_pid(Pid)->
+ inet:peername(Socket);
+peername(#sslsocket{pid = {ListenSocket, _}}) ->
+ inet:peername(ListenSocket). %% Will return {error, enotconn}
%%--------------------------------------------------------------------
-spec peercert(#sslsocket{}) ->{ok, DerCert::binary()} | {error, reason()}.
%%
%% Description: Returns the peercert.
%%--------------------------------------------------------------------
-peercert(#sslsocket{pid = Pid}) ->
+peercert(#sslsocket{pid = Pid}) when is_pid(Pid) ->
case ssl_connection:peer_certificate(Pid) of
{ok, undefined} ->
{error, no_peercert};
Result ->
Result
- end.
+ end;
+peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
+ {error, enotconn}.
%%--------------------------------------------------------------------
-spec suite_definition(cipher_suite()) -> erl_cipher_suite().
@@ -314,6 +334,14 @@ suite_definition(S) ->
{KeyExchange, Cipher, Hash}.
%%--------------------------------------------------------------------
+-spec negotiated_next_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}.
+%%
+%% Description: Returns the next protocol that has been negotiated. If no
+%% protocol has been negotiated will return {error, next_protocol_not_negotiated}
+%%--------------------------------------------------------------------
+negotiated_next_protocol(#sslsocket{pid = Pid}) ->
+ ssl_connection:negotiated_next_protocol(Pid).
+
-spec cipher_suites() -> [erl_cipher_suite()].
-spec cipher_suites(erlang | openssl) -> [erl_cipher_suite()] | [string()].
@@ -384,8 +412,9 @@ setopts(#sslsocket{}, Options) ->
%%
%% Description: Same as gen_tcp:shutdown/2
%%--------------------------------------------------------------------
-shutdown(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _, _}}}}, How) ->
- CbMod:shutdown(ListenSocket, How);
+shutdown(#sslsocket{pid = {Listen, #config{cb={CbMod,_, _, _}}}},
+ How) when is_port(Listen) ->
+ CbMod:shutdown(Listen, How);
shutdown(#sslsocket{pid = Pid}, How) ->
ssl_connection:shutdown(Pid, How).
@@ -394,11 +423,11 @@ shutdown(#sslsocket{pid = Pid}, How) ->
%%
%% Description: Same as inet:sockname/1
%%--------------------------------------------------------------------
-sockname(#sslsocket{pid = {ListenSocket, _}}) ->
- inet:sockname(ListenSocket);
+sockname(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
+ inet:sockname(Listen);
-sockname(#sslsocket{pid = Pid}) ->
- ssl_connection:sockname(Pid).
+sockname(#sslsocket{pid = Pid, fd = Socket}) when is_pid(Pid) ->
+ inet:sockname(Socket).
%%---------------------------------------------------------------
-spec session_info(#sslsocket{}) -> {ok, list()} | {error, reason()}.
@@ -406,12 +435,14 @@ sockname(#sslsocket{pid = Pid}) ->
%% Description: Returns list of session info currently [{session_id, session_id(),
%% {cipher_suite, cipher_suite()}]
%%--------------------------------------------------------------------
-session_info(#sslsocket{pid = Pid, fd = new_ssl}) ->
- ssl_connection:session_info(Pid).
+session_info(#sslsocket{pid = Pid}) when is_pid(Pid) ->
+ ssl_connection:session_info(Pid);
+session_info(#sslsocket{pid = {Listen,_}}) when is_port(Listen) ->
+ {error, enotconn}.
%%---------------------------------------------------------------
-spec versions() -> [{ssl_app, string()} | {supported, [tls_atom_version()]} |
- {available, [tls_atom_version()]}].
+ {available, [tls_atom_version()]}].
%%
%% Description: Returns a list of relevant versions.
%%--------------------------------------------------------------------
@@ -427,8 +458,10 @@ versions() ->
%%
%% Description: Initiates a renegotiation.
%%--------------------------------------------------------------------
-renegotiate(#sslsocket{pid = Pid, fd = new_ssl}) ->
- ssl_connection:renegotiation(Pid).
+renegotiate(#sslsocket{pid = Pid}) when is_pid(Pid) ->
+ ssl_connection:renegotiation(Pid);
+renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) ->
+ {error, enotconn}.
%%--------------------------------------------------------------------
-spec prf(#sslsocket{}, binary() | 'master_secret', binary(),
@@ -437,10 +470,11 @@ renegotiate(#sslsocket{pid = Pid, fd = new_ssl}) ->
%%
%% Description: use a ssl sessions TLS PRF to generate key material
%%--------------------------------------------------------------------
-prf(#sslsocket{pid = Pid, fd = new_ssl},
- Secret, Label, Seed, WantedLength) ->
- ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength).
-
+prf(#sslsocket{pid = Pid},
+ Secret, Label, Seed, WantedLength) when is_pid(Pid) ->
+ ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength);
+prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) ->
+ {error, enotconn}.
%%--------------------------------------------------------------------
-spec clear_pem_cache() -> ok.
@@ -594,7 +628,9 @@ 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),
+ next_protocols_advertised = handle_option(next_protocols_advertised, Opts, undefined),
+ next_protocol_selector = make_next_protocol_selector(handle_option(client_preferred_next_protocols, Opts, undefined))
},
CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
@@ -603,7 +639,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, next_protocols_advertised,
+ client_preferred_next_protocols],
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
@@ -728,12 +765,64 @@ validate_option(hibernate_after, undefined) ->
undefined;
validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 ->
Value;
-validate_option(erl_dist,Value) when Value == true;
+validate_option(erl_dist,Value) when Value == true;
Value == false ->
Value;
+validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols} = Value)
+ when is_list(PreferredProtocols) ->
+ case ssl_record:highest_protocol_version([]) of
+ {3,0} ->
+ throw({error, {eoptions, {not_supported_in_sslv3, {Opt, Value}}}});
+ _ ->
+ validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
+ validate_npn_ordering(Precedence),
+ {Precedence, PreferredProtocols, ?NO_PROTOCOL}
+ end;
+validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols, Default} = Value)
+ when is_list(PreferredProtocols), is_binary(Default),
+ byte_size(Default) > 0, byte_size(Default) < 256 ->
+ case ssl_record:highest_protocol_version([]) of
+ {3,0} ->
+ throw({error, {eoptions, {not_supported_in_sslv3, {Opt, Value}}}});
+ _ ->
+ validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
+ validate_npn_ordering(Precedence),
+ Value
+ end;
+
+validate_option(client_preferred_next_protocols, undefined) ->
+ undefined;
+validate_option(next_protocols_advertised = Opt, Value) when is_list(Value) ->
+ case ssl_record:highest_protocol_version([]) of
+ {3,0} ->
+ throw({error, {eoptions, {not_supported_in_sslv3, {Opt, Value}}}});
+ _ ->
+ validate_binary_list(next_protocols_advertised, Value),
+ Value
+ end;
+
+validate_option(next_protocols_advertised, undefined) ->
+ undefined;
validate_option(Opt, Value) ->
throw({error, {eoptions, {Opt, Value}}}).
-
+
+validate_npn_ordering(client) ->
+ ok;
+validate_npn_ordering(server) ->
+ ok;
+validate_npn_ordering(Value) ->
+ throw({error, {eoptions, {client_preferred_next_protocols, {invalid_precedence, Value}}}}).
+
+validate_binary_list(Opt, List) ->
+ lists:foreach(
+ fun(Bin) when is_binary(Bin),
+ byte_size(Bin) > 0,
+ byte_size(Bin) < 256 ->
+ ok;
+ (Bin) ->
+ throw({error, {eoptions, {Opt, {invalid_protocol, Bin}}}})
+ end, List).
+
validate_versions([], Versions) ->
Versions;
validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2';
@@ -839,6 +928,34 @@ cipher_suites(Version, Ciphers0) ->
no_format(Error) ->
lists:flatten(io_lib:format("No format string for error: \"~p\" available.", [Error])).
+
+detect(_Pred, []) ->
+ undefined;
+detect(Pred, [H|T]) ->
+ case Pred(H) of
+ true ->
+ H;
+ _ ->
+ detect(Pred, T)
+ end.
+
+make_next_protocol_selector(undefined) ->
+ undefined;
+make_next_protocol_selector({client, AllProtocols, DefaultProtocol}) ->
+ fun(AdvertisedProtocols) ->
+ case detect(fun(PreferredProtocol) -> lists:member(PreferredProtocol, AdvertisedProtocols) end, AllProtocols) of
+ undefined -> DefaultProtocol;
+ PreferredProtocol -> PreferredProtocol
+ end
+ end;
+
+make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) ->
+ fun(AdvertisedProtocols) ->
+ case detect(fun(PreferredProtocol) -> lists:member(PreferredProtocol, AllProtocols) end, AdvertisedProtocols) of
+ undefined -> DefaultProtocol;
+ PreferredProtocol -> PreferredProtocol
+ end
+ end.
%% Only used to remove exit messages from old ssl
%% First is a nonsense clause to provide some
@@ -846,7 +963,5 @@ no_format(Error) ->
%% function in a none recommended way, but will
%% work correctly if a valid pid is returned.
%% Deprcated to be removed in r16
-pid(#sslsocket{fd = new_ssl}) ->
- whereis(ssl_connection_sup);
-pid(#sslsocket{pid = Pid}) ->
- Pid.
+pid(#sslsocket{})->
+ whereis(ssl_connection_sup).
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index eb71bc61e9..cde13069b5 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -40,8 +40,7 @@
-export([send/2, recv/3, connect/7, ssl_accept/6, handshake/2,
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]).
+ peer_certificate/1, renegotiation/1, negotiated_next_protocol/1, prf/5]).
%% Called by ssl_connection_sup
-export([start_link/7]).
@@ -90,9 +89,12 @@
log_alert, % boolean()
renegotiation, % {boolean(), From | internal | peer}
start_or_recv_from, % "gen_fsm From"
+ timer, % start_or_recv_timer
send_queue, % queue()
terminated = false, %
- allow_renegotiate = true
+ allow_renegotiate = true,
+ expecting_next_protocol_negotiation = false :: boolean(),
+ next_protocol = undefined :: undefined | binary()
}).
-define(DEFAULT_DIFFIE_HELLMAN_PARAMS,
@@ -179,7 +181,7 @@ handshake(#sslsocket{pid = Pid}, Timeout) ->
socket_control(Socket, Pid, CbModule) ->
case CbModule:controlling_process(Socket, Pid) of
ok ->
- {ok, sslsocket(Pid)};
+ {ok, sslsocket(Pid, Socket)};
{error, Reason} ->
{error, Reason}
end.
@@ -213,20 +215,15 @@ shutdown(ConnectionPid, How) ->
%%--------------------------------------------------------------------
new_user(ConnectionPid, User) ->
sync_send_all_state_event(ConnectionPid, {new_user, User}).
+
%%--------------------------------------------------------------------
--spec sockname(pid()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
-%%
-%% Description: Same as inet:sockname/1
-%%--------------------------------------------------------------------
-sockname(ConnectionPid) ->
- sync_send_all_state_event(ConnectionPid, sockname).
-%%--------------------------------------------------------------------
--spec peername(pid()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
+-spec negotiated_next_protocol(pid()) -> {ok, binary()} | {error, reason()}.
%%
-%% Description: Same as inet:peername/1
+%% Description: Returns the negotiated protocol
%%--------------------------------------------------------------------
-peername(ConnectionPid) ->
- sync_send_all_state_event(ConnectionPid, peername).
+negotiated_next_protocol(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, negotiated_next_protocol).
+
%%--------------------------------------------------------------------
-spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}.
%%
@@ -374,16 +371,29 @@ hello(#server_hello{cipher_suite = CipherSuite,
renegotiation = {Renegotiation, _},
ssl_options = SslOptions} = State0) ->
case ssl_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
- {Version, NewId, ConnectionStates} ->
+ #alert{} = Alert ->
+ handle_own_alert(Alert, ReqVersion, hello, State0),
+ {stop, {shutdown, own_alert}, State0};
+ {Version, NewId, ConnectionStates, NextProtocol} ->
{KeyAlgorithm, _, _, _} =
ssl_cipher:suite_definition(CipherSuite),
-
+
PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
+
+ NewNextProtocol = case NextProtocol of
+ undefined ->
+ State0#state.next_protocol;
+ _ ->
+ NextProtocol
+ end,
+
State = State0#state{key_algorithm = KeyAlgorithm,
hashsign_algorithm = default_hashsign(Version, KeyAlgorithm),
negotiated_version = Version,
connection_states = ConnectionStates,
- premaster_secret = PremasterSecret},
+ premaster_secret = PremasterSecret,
+ expecting_next_protocol_negotiation = NextProtocol =/= undefined,
+ next_protocol = NewNextProtocol},
case ssl_session:is_new(OldId, NewId) of
true ->
@@ -391,13 +401,10 @@ hello(#server_hello{cipher_suite = CipherSuite,
State#state{connection_states = ConnectionStates});
false ->
handle_resumed_session(NewId, State#state{connection_states = ConnectionStates})
- end;
- #alert{} = Alert ->
- handle_own_alert(Alert, ReqVersion, hello, State0),
- {stop, {shutdown, own_alert}, State0}
+ end
end;
-hello(Hello = #client_hello{client_version = ClientVersion},
+hello(Hello = #client_hello{client_version = ClientVersion},
State = #state{connection_states = ConnectionStates0,
port = Port, session = #session{own_certificate = Cert} = Session0,
renegotiation = {Renegotiation, _},
@@ -406,8 +413,8 @@ hello(Hello = #client_hello{client_version = ClientVersion},
ssl_options = SslOpts}) ->
case ssl_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
ConnectionStates0, Cert}, Renegotiation) of
- {Version, {Type, Session}, ConnectionStates} ->
- do_server_hello(Type, State#state{connection_states =
+ {Version, {Type, Session}, ConnectionStates, ProtocolsToAdvertise} ->
+ do_server_hello(Type, ProtocolsToAdvertise, State#state{connection_states =
ConnectionStates,
negotiated_version = Version,
session = Session});
@@ -583,6 +590,7 @@ certify(#client_key_exchange{exchange_keys = Keys},
handle_own_alert(Alert, Version, certify, State)
end;
+
certify(timeout, State) ->
{ next_state, certify, State, hibernate };
@@ -649,6 +657,12 @@ cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashS
handle_own_alert(Alert, Version, cipher, State0)
end;
+% client must send a next protocol message if we are expecting it
+cipher(#finished{}, #state{role = server, expecting_next_protocol_negotiation = true,
+ next_protocol = undefined, negotiated_version = Version} = State0) ->
+ handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0),
+ {stop, normal, State0};
+
cipher(#finished{verify_data = Data} = Finished,
#state{negotiated_version = Version,
host = Host,
@@ -669,6 +683,13 @@ cipher(#finished{verify_data = Data} = Finished,
handle_own_alert(Alert, Version, cipher, State)
end;
+% only allowed to send next_protocol message after change cipher spec
+% & before finished message and it is not allowed during renegotiation
+cipher(#next_protocol{selected_protocol = SelectedProtocol},
+ #state{role = server, expecting_next_protocol_negotiation = true} = State0) ->
+ {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}),
+ next_state(cipher, cipher, Record, State);
+
cipher(timeout, State) ->
{ next_state, cipher, State, hibernate };
@@ -755,8 +776,9 @@ handle_sync_event({application_data, Data}, From, StateName,
get_timeout(State)};
handle_sync_event({start, Timeout}, StartFrom, hello, State) ->
- start_or_recv_cancel_timer(Timeout, StartFrom),
- hello(start, State#state{start_or_recv_from = StartFrom});
+ Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
+ hello(start, State#state{start_or_recv_from = StartFrom,
+ timer = Timer});
%% The two clauses below could happen if a server upgrades a socket in
%% active mode. Note that in this case we are lucky that
@@ -772,8 +794,9 @@ handle_sync_event({start,_}, _From, error, {Error, State = #state{}}) ->
{stop, {shutdown, Error}, {error, Error}, State};
handle_sync_event({start, Timeout}, StartFrom, StateName, State) ->
- start_or_recv_cancel_timer(Timeout, StartFrom),
- {next_state, StateName, State#state{start_or_recv_from = StartFrom}, get_timeout(State)};
+ Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
+ {next_state, StateName, State#state{start_or_recv_from = StartFrom,
+ timer = Timer}, get_timeout(State)};
handle_sync_event(close, _, StateName, State) ->
%% Run terminate before returning
@@ -805,14 +828,16 @@ handle_sync_event({shutdown, How0}, _, StateName,
end;
handle_sync_event({recv, N, Timeout}, RecvFrom, connection = StateName, State0) ->
- start_or_recv_cancel_timer(Timeout, RecvFrom),
- passive_receive(State0#state{bytes_to_read = N, start_or_recv_from = RecvFrom}, StateName);
+ Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
+ passive_receive(State0#state{bytes_to_read = N,
+ start_or_recv_from = RecvFrom, timer = Timer}, StateName);
%% Doing renegotiate wait with handling request until renegotiate is
%% finished. Will be handled by next_state_is_connection/2.
handle_sync_event({recv, N, Timeout}, RecvFrom, StateName, State) ->
- start_or_recv_cancel_timer(Timeout, RecvFrom),
- {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom},
+ Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
+ {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom,
+ timer = Timer},
get_timeout(State)};
handle_sync_event({new_user, User}, _From, StateName,
@@ -828,15 +853,10 @@ handle_sync_event({get_opts, OptTags}, _From, StateName,
OptsReply = get_socket_opts(Socket, OptTags, SockOpts, []),
{reply, OptsReply, StateName, State, get_timeout(State)};
-handle_sync_event(sockname, _From, StateName,
- #state{socket = Socket} = State) ->
- SockNameReply = inet:sockname(Socket),
- {reply, SockNameReply, StateName, State, get_timeout(State)};
-
-handle_sync_event(peername, _From, StateName,
- #state{socket = Socket} = State) ->
- PeerNameReply = inet:peername(Socket),
- {reply, PeerNameReply, StateName, State, get_timeout(State)};
+handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = undefined} = State) ->
+ {reply, {error, next_protocol_not_negotiated}, StateName, State, get_timeout(State)};
+handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = NextProtocol} = State) ->
+ {reply, {ok, NextProtocol}, StateName, State, get_timeout(State)};
handle_sync_event({set_opts, Opts0}, _From, StateName,
#state{socket_options = Opts1,
@@ -965,7 +985,7 @@ handle_info({CloseTag, Socket}, StateName,
handle_info({ErrorTag, Socket, econnaborted}, StateName,
#state{socket = Socket, start_or_recv_from = StartFrom, role = Role,
error_tag = ErrorTag} = State) when StateName =/= connection ->
- alert_user(StartFrom, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Role),
+ alert_user(Socket, StartFrom, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Role),
{stop, normal, State};
handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket,
@@ -981,13 +1001,20 @@ handle_info({'DOWN', MonitorRef, _, _, _}, _,
handle_info(allow_renegotiate, StateName, State) ->
{next_state, StateName, State#state{allow_renegotiate = true}, get_timeout(State)};
-
-handle_info({cancel_start_or_recv, RecvFrom}, connection = StateName, #state{start_or_recv_from = RecvFrom} = State) ->
+
+handle_info({cancel_start_or_recv, StartFrom}, StateName,
+ #state{renegotiation = {false, first}} = State) when StateName =/= connection ->
+ gen_fsm:reply(StartFrom, {error, timeout}),
+ {stop, {shutdown, user_timeout}, State#state{timer = undefined}};
+
+handle_info({cancel_start_or_recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom} = State) ->
gen_fsm:reply(RecvFrom, {error, timeout}),
- {next_state, StateName, State#state{start_or_recv_from = undefined}, get_timeout(State)};
+ {next_state, StateName, State#state{start_or_recv_from = undefined,
+ bytes_to_read = undefined,
+ timer = undefined}, get_timeout(State)};
handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) ->
- {next_state, StateName, State, get_timeout(State)};
+ {next_state, StateName, State#state{timer = undefined}, get_timeout(State)};
handle_info(Msg, StateName, State) ->
Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [Msg]),
@@ -1279,17 +1306,18 @@ verify_client_cert(#state{client_certificate_requested = true, role = client,
verify_client_cert(#state{client_certificate_requested = false} = State) ->
State.
-do_server_hello(Type, #state{negotiated_version = Version,
- session = #session{session_id = SessId},
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}}
- = State0) when is_atom(Type) ->
+do_server_hello(Type, NextProtocolsToSend, #state{negotiated_version = Version,
+ session = #session{session_id = SessId},
+ connection_states = ConnectionStates0,
+ renegotiation = {Renegotiation, _}}
+ = State0) when is_atom(Type) ->
ServerHello =
ssl_handshake:server_hello(SessId, Version,
- ConnectionStates0, Renegotiation),
- State = server_hello(ServerHello, State0),
-
+ ConnectionStates0, Renegotiation, NextProtocolsToSend),
+ State = server_hello(ServerHello,
+ State0#state{expecting_next_protocol_negotiation =
+ NextProtocolsToSend =/= undefined}),
case Type of
new ->
new_server_hello(ServerHello, State);
@@ -1539,12 +1567,33 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} =
State.
finalize_handshake(State, StateName) ->
- ConnectionStates0 = cipher_protocol(State),
+ ConnectionStates0 = cipher_protocol(State),
+
ConnectionStates =
ssl_record:activate_pending_connection_state(ConnectionStates0,
write),
- finished(State#state{connection_states = ConnectionStates}, StateName).
-
+
+ State1 = State#state{connection_states = ConnectionStates},
+ State2 = next_protocol(State1),
+ finished(State2, StateName).
+
+next_protocol(#state{role = server} = State) ->
+ State;
+next_protocol(#state{next_protocol = undefined} = State) ->
+ State;
+next_protocol(#state{expecting_next_protocol_negotiation = false} = State) ->
+ State;
+next_protocol(#state{transport_cb = Transport, socket = Socket,
+ negotiated_version = Version,
+ next_protocol = NextProtocol,
+ connection_states = ConnectionStates0,
+ tls_handshake_history = Handshake0} = State) ->
+ NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
+ {BinMsg, ConnectionStates, Handshake} = encode_handshake(NextProtocolMessage, Version, ConnectionStates0, Handshake0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates,
+ tls_handshake_history = Handshake}.
+
cipher_protocol(#state{connection_states = ConnectionStates0,
socket = Socket,
negotiated_version = Version,
@@ -1729,10 +1778,12 @@ passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
end.
read_application_data(Data, #state{user_application = {_Mon, Pid},
- socket_options = SOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = Buffer0} = State0) ->
+ socket = Socket,
+ socket_options = SOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ timer = Timer,
+ user_data_buffer = Buffer0} = State0) ->
Buffer1 = if
Buffer0 =:= <<>> -> Data;
Data =:= <<>> -> Buffer0;
@@ -1740,10 +1791,12 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
end,
case get_data(SOpts, BytesToRead, Buffer1) of
{ok, ClientData, Buffer} -> % Send data
- SocketOpt = deliver_app_data(SOpts, ClientData, Pid, RecvFrom),
+ SocketOpt = deliver_app_data(Socket, SOpts, ClientData, Pid, RecvFrom),
+ cancel_timer(Timer),
State = State0#state{user_data_buffer = Buffer,
start_or_recv_from = undefined,
- bytes_to_read = 0,
+ timer = undefined,
+ bytes_to_read = undefined,
socket_options = SocketOpt
},
if
@@ -1756,8 +1809,10 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
end;
{more, Buffer} -> % no reply, we need more data
next_record(State0#state{user_data_buffer = Buffer});
+ {passive, Buffer} ->
+ next_record_if_active(State0#state{user_data_buffer = Buffer});
{error,_Reason} -> %% Invalid packet in packet mode
- deliver_packet_error(SOpts, Buffer1, Pid, RecvFrom),
+ deliver_packet_error(Socket, SOpts, Buffer1, Pid, RecvFrom),
{stop, normal, State0}
end.
@@ -1797,6 +1852,9 @@ is_time_to_renegotiate(_,_) ->
%% Picks ClientData
get_data(_, _, <<>>) ->
{more, <<>>};
+%% Recv timed out save buffer data until next recv
+get_data(#socket_options{active=false}, undefined, Buffer) ->
+ {passive, Buffer};
get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer)
when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
if
@@ -1836,9 +1894,9 @@ decode_packet(Type, Buffer, PacketOpts) ->
%% Note that if the user has explicitly configured the socket to expect
%% HTTP headers using the {packet, httph} option, we don't do any automatic
%% switching of states.
-deliver_app_data(SOpts = #socket_options{active=Active, packet=Type},
- Data, Pid, From) ->
- send_or_reply(Active, Pid, From, format_reply(SOpts, Data)),
+deliver_app_data(Socket, SOpts = #socket_options{active=Active, packet=Type},
+ Data, Pid, From) ->
+ send_or_reply(Active, Pid, From, format_reply(Socket, SOpts, Data)),
SO = case Data of
{P, _, _, _} when ((P =:= http_request) or (P =:= http_response)),
((Type =:= http) or (Type =:= http_bin)) ->
@@ -1857,31 +1915,31 @@ deliver_app_data(SOpts = #socket_options{active=Active, packet=Type},
SO
end.
-format_reply(#socket_options{active = false, mode = Mode, packet = Packet,
+format_reply(_,#socket_options{active = false, mode = Mode, packet = Packet,
header = Header}, Data) ->
- {ok, format_reply(Mode, Packet, Header, Data)};
-format_reply(#socket_options{active = _, mode = Mode, packet = Packet,
+ {ok, do_format_reply(Mode, Packet, Header, Data)};
+format_reply(Socket, #socket_options{active = _, mode = Mode, packet = Packet,
header = Header}, Data) ->
- {ssl, sslsocket(), format_reply(Mode, Packet, Header, Data)}.
+ {ssl, sslsocket(self(), Socket), do_format_reply(Mode, Packet, Header, Data)}.
-deliver_packet_error(SO= #socket_options{active = Active}, Data, Pid, From) ->
- send_or_reply(Active, Pid, From, format_packet_error(SO, Data)).
+deliver_packet_error(Socket, SO= #socket_options{active = Active}, Data, Pid, From) ->
+ send_or_reply(Active, Pid, From, format_packet_error(Socket, SO, Data)).
-format_packet_error(#socket_options{active = false, mode = Mode}, Data) ->
- {error, {invalid_packet, format_reply(Mode, raw, 0, Data)}};
-format_packet_error(#socket_options{active = _, mode = Mode}, Data) ->
- {ssl_error, sslsocket(), {invalid_packet, format_reply(Mode, raw, 0, Data)}}.
+format_packet_error(_,#socket_options{active = false, mode = Mode}, Data) ->
+ {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
+format_packet_error(Socket, #socket_options{active = _, mode = Mode}, Data) ->
+ {ssl_error, sslsocket(self(), Socket), {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
-format_reply(binary, _, N, Data) when N > 0 -> % Header mode
+do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
header(N, Data);
-format_reply(binary, _, _, Data) ->
+do_format_reply(binary, _, _, Data) ->
Data;
-format_reply(list, Packet, _, Data)
+do_format_reply(list, Packet, _, Data)
when Packet == http; Packet == {http, headers};
Packet == http_bin; Packet == {http_bin, headers};
Packet == httph; Packet == httph_bin ->
Data;
-format_reply(list, _,_, Data) ->
+do_format_reply(list, _,_, Data) ->
binary_to_list(Data).
header(0, <<>>) ->
@@ -2052,8 +2110,8 @@ next_state_is_connection(_, State =
next_state_is_connection(StateName, State0) ->
{Record, State} = next_record_if_active(State0),
next_state(StateName, connection, Record, State#state{premaster_secret = undefined,
- public_key_info = undefined,
- tls_handshake_history = ssl_handshake:init_handshake_history()}).
+ public_key_info = undefined,
+ tls_handshake_history = ssl_handshake:init_handshake_history()}).
register_session(client, Host, Port, #session{is_resumable = new} = Session0) ->
Session = Session0#session{is_resumable = true},
@@ -2102,7 +2160,6 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
tls_record_buffer = <<>>,
tls_cipher_texts = [],
user_application = {Monitor, User},
- bytes_to_read = 0,
user_data_buffer = <<>>,
log_alert = true,
session_cache_cb = SessionCacheCb,
@@ -2111,11 +2168,8 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
send_queue = queue:new()
}.
-sslsocket(Pid) ->
- #sslsocket{pid = Pid, fd = new_ssl}.
-
-sslsocket() ->
- sslsocket(self()).
+sslsocket(Pid, Socket) ->
+ #sslsocket{pid = Pid, fd = Socket}.
get_socket_opts(_,[], _, Acc) ->
{ok, Acc};
@@ -2211,12 +2265,12 @@ handle_alerts([Alert | Alerts], {next_state, StateName, State, _Timeout}) ->
handle_alerts(Alerts, handle_alert(Alert, StateName, State)).
handle_alert(#alert{level = ?FATAL} = Alert, StateName,
- #state{start_or_recv_from = From, host = Host, port = Port, session = Session,
- user_application = {_Mon, Pid},
+ #state{socket = Socket, start_or_recv_from = From, host = Host,
+ port = Port, session = Session, user_application = {_Mon, Pid},
log_alert = Log, role = Role, socket_options = Opts} = State) ->
invalidate_session(Role, Host, Port, Session),
log_alert(Log, StateName, Alert),
- alert_user(StateName, Opts, Pid, From, Alert, Role),
+ alert_user(Socket, StateName, Opts, Pid, From, Alert, Role),
{stop, normal, State};
handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
@@ -2243,28 +2297,28 @@ handle_alert(#alert{level = ?WARNING, description = ?USER_CANCELED} = Alert, Sta
{Record, State} = next_record(State0),
next_state(StateName, StateName, Record, State).
-alert_user(connection, Opts, Pid, From, Alert, Role) ->
- alert_user(Opts#socket_options.active, Pid, From, Alert, Role);
-alert_user(_, _, _, From, Alert, Role) ->
- alert_user(From, Alert, Role).
+alert_user(Socket, connection, Opts, Pid, From, Alert, Role) ->
+ alert_user(Socket, Opts#socket_options.active, Pid, From, Alert, Role);
+alert_user(Socket,_, _, _, From, Alert, Role) ->
+ alert_user(Socket, From, Alert, Role).
-alert_user(From, Alert, Role) ->
- alert_user(false, no_pid, From, Alert, Role).
+alert_user(Socket, From, Alert, Role) ->
+ alert_user(Socket, false, no_pid, From, Alert, Role).
-alert_user(false = Active, Pid, From, Alert, Role) ->
+alert_user(_Socket, false = Active, Pid, From, Alert, Role) ->
%% If there is an outstanding ssl_accept | recv
%% From will be defined and send_or_reply will
%% send the appropriate error message.
ReasonCode = ssl_alert:reason_code(Alert, Role),
send_or_reply(Active, Pid, From, {error, ReasonCode});
-alert_user(Active, Pid, From, Alert, Role) ->
+alert_user(Socket, Active, Pid, From, Alert, Role) ->
case ssl_alert:reason_code(Alert, Role) of
closed ->
send_or_reply(Active, Pid, From,
- {ssl_closed, sslsocket()});
+ {ssl_closed, sslsocket(self(), Socket)});
ReasonCode ->
send_or_reply(Active, Pid, From,
- {ssl_error, sslsocket(), ReasonCode})
+ {ssl_error, sslsocket(self(), Socket), ReasonCode})
end.
log_alert(true, Info, Alert) ->
@@ -2294,13 +2348,16 @@ handle_own_alert(Alert, Version, StateName,
end,
{stop, {shutdown, own_alert}, State}.
-handle_normal_shutdown(Alert, _, #state{start_or_recv_from = StartFrom, role = Role, renegotiation = {false, first}}) ->
- alert_user(StartFrom, Alert, Role);
+handle_normal_shutdown(Alert, _, #state{socket = Socket,
+ start_or_recv_from = StartFrom,
+ role = Role, renegotiation = {false, first}}) ->
+ alert_user(Socket, StartFrom, Alert, Role);
-handle_normal_shutdown(Alert, StateName, #state{socket_options = Opts,
+handle_normal_shutdown(Alert, StateName, #state{socket = Socket,
+ socket_options = Opts,
user_application = {_Mon, Pid},
start_or_recv_from = RecvFrom, role = Role}) ->
- alert_user(StateName, Opts, Pid, RecvFrom, Alert, Role).
+ alert_user(Socket, StateName, Opts, Pid, RecvFrom, Alert, Role).
handle_unexpected_message(Msg, Info, #state{negotiated_version = Version} = State) ->
Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE),
@@ -2325,9 +2382,11 @@ ack_connection(#state{renegotiation = {true, From}} = State) ->
gen_fsm:reply(From, ok),
State#state{renegotiation = undefined};
ack_connection(#state{renegotiation = {false, first},
- start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined ->
+ start_or_recv_from = StartFrom,
+ timer = Timer} = State) when StartFrom =/= undefined ->
gen_fsm:reply(StartFrom, connected),
- State#state{renegotiation = undefined, start_or_recv_from = undefined};
+ cancel_timer(Timer),
+ State#state{renegotiation = undefined, start_or_recv_from = undefined, timer = undefined};
ack_connection(State) ->
State.
@@ -2465,10 +2524,15 @@ default_hashsign(_Version, KeyExchange)
{null, anon}.
start_or_recv_cancel_timer(infinity, _RecvFrom) ->
- ok;
+ undefined;
start_or_recv_cancel_timer(Timeout, RecvFrom) ->
erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}).
+cancel_timer(undefined) ->
+ ok;
+cancel_timer(Timer) ->
+ erlang:cancel_timer(Timer).
+
handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport} = State) ->
inet:setopts(Socket, [{active, false}]),
case Transport:recv(Socket, 0, 0) of
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index bb26302fff..db21dac942 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -30,21 +30,21 @@
-include("ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([master_secret/4, client_hello/8, server_hello/4, hello/4,
+-export([master_secret/4, client_hello/8, server_hello/5, hello/4,
hello_request/0, certify/7, certificate/4,
client_certificate_verify/6, certificate_verify/6,
certificate_request/3, key_exchange/3, server_key_exchange_hash/2,
finished/5, verify_connection/6, get_tls_handshake/3,
decode_client_key/3, server_hello_done/0,
encode_handshake/2, init_handshake_history/0, update_handshake_history/2,
- decrypt_premaster_secret/2, prf/5]).
+ decrypt_premaster_secret/2, prf/5, next_protocol/1]).
-export([dec_hello_extensions/2]).
-type tls_handshake() :: #client_hello{} | #server_hello{} |
#server_hello_done{} | #certificate{} | #certificate_request{} |
#client_key_exchange{} | #finished{} | #certificate_verify{} |
- #hello_request{}.
+ #hello_request{} | #next_protocol{}.
%%====================================================================
%% Internal application API
@@ -77,18 +77,31 @@ client_hello(Host, Port, ConnectionStates,
cipher_suites = cipher_suites(Ciphers, Renegotiation),
compression_methods = ssl_record:compressions(),
random = SecParams#security_parameters.client_random,
+
renegotiation_info =
renegotiation_info(client, ConnectionStates, Renegotiation),
- hash_signs = default_hash_signs()
+ hash_signs = default_hash_signs(),
+ next_protocol_negotiation =
+ encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, Renegotiation)
}.
+encode_protocol(Protocol, Acc) ->
+ Len = byte_size(Protocol),
+ <<Acc/binary, ?BYTE(Len), Protocol/binary>>.
+
+encode_protocols_advertised_on_server(undefined) ->
+ undefined;
+
+encode_protocols_advertised_on_server(Protocols) ->
+ #next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
+
%%--------------------------------------------------------------------
-spec server_hello(session_id(), tls_version(), #connection_states{},
- boolean()) -> #server_hello{}.
+ boolean(), [binary()] | undefined) -> #server_hello{}.
%%
%% Description: Creates a server hello message.
%%--------------------------------------------------------------------
-server_hello(SessionId, Version, ConnectionStates, Renegotiation) ->
+server_hello(SessionId, Version, ConnectionStates, Renegotiation, ProtocolsAdvertisedOnServer) ->
Pending = ssl_record:pending_connection_state(ConnectionStates, read),
SecParams = Pending#connection_state.security_parameters,
#server_hello{server_version = Version,
@@ -98,7 +111,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),
+ next_protocol_negotiation = encode_protocols_advertised_on_server(ProtocolsAdvertisedOnServer)
}.
%%--------------------------------------------------------------------
@@ -113,20 +127,21 @@ hello_request() ->
%%--------------------------------------------------------------------
-spec hello(#server_hello{} | #client_hello{}, #ssl_options{},
#connection_states{} | {inet:port_number(), #session{}, db_handle(),
- atom(), #connection_states{}, binary()},
- boolean()) -> {tls_version(), session_id(), #connection_states{}}|
- {tls_version(), {resumed | new, #session{}},
- #connection_states{}} | #alert{}.
+ atom(), #connection_states{}, binary()},
+ boolean()) ->
+ {tls_version(), session_id(), #connection_states{}, binary() | undefined}|
+ {tls_version(), {resumed | new, #session{}}, #connection_states{}, list(binary()) | undefined} |
+ #alert{}.
%%
%% Description: Handles a recieved hello message
%%--------------------------------------------------------------------
hello(#server_hello{cipher_suite = CipherSuite, server_version = Version,
compression_method = Compression, random = Random,
session_id = SessionId, renegotiation_info = Info,
- hash_signs = _HashSigns},
- #ssl_options{secure_renegotiate = SecureRenegotation},
+ hash_signs = _HashSigns} = Hello,
+ #ssl_options{secure_renegotiate = SecureRenegotation, next_protocol_selector = NextProtocolSelector},
ConnectionStates0, Renegotiation) ->
-%%TODO: select hash and signature algorigthm
+ %%TODO: select hash and signature algorigthm
case ssl_record:is_acceptable_version(Version) of
true ->
case handle_renegotiation_info(client, Info, ConnectionStates0,
@@ -135,7 +150,12 @@ hello(#server_hello{cipher_suite = CipherSuite, server_version = Version,
ConnectionStates =
hello_pending_connection_states(client, Version, CipherSuite, Random,
Compression, ConnectionStates1),
- {Version, SessionId, ConnectionStates};
+ case handle_next_protocol(Hello, NextProtocolSelector, Renegotiation) of
+ #alert{} = Alert ->
+ Alert;
+ Protocol ->
+ {Version, SessionId, ConnectionStates, Protocol}
+ end;
#alert{} = Alert ->
Alert
end;
@@ -145,9 +165,8 @@ hello(#server_hello{cipher_suite = CipherSuite, server_version = Version,
hello(#client_hello{client_version = ClientVersion, random = Random,
cipher_suites = CipherSuites,
- renegotiation_info = Info,
- hash_signs = _HashSigns} = Hello,
- #ssl_options{versions = Versions,
+ renegotiation_info = Info} = Hello,
+ #ssl_options{versions = Versions,
secure_renegotiate = SecureRenegotation} = SslOpts,
{Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) ->
%% TODO: select hash and signature algorithm
@@ -173,7 +192,12 @@ hello(#client_hello{client_version = ClientVersion, random = Random,
Random,
Compression,
ConnectionStates1),
- {Version, {Type, Session}, ConnectionStates};
+ case handle_next_protocol_on_server(Hello, Renegotiation, SslOpts) of
+ #alert{} = Alert ->
+ Alert;
+ ProtocolsToAdvertise ->
+ {Version, {Type, Session}, ConnectionStates, ProtocolsToAdvertise}
+ end;
#alert{} = Alert ->
Alert
end
@@ -427,6 +451,11 @@ master_secret(Version, PremasterSecret, ConnectionStates, Role) ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
end.
+-spec next_protocol(binary()) -> #next_protocol{}.
+
+next_protocol(SelectedProtocol) ->
+ #next_protocol{selected_protocol = SelectedProtocol}.
+
%%--------------------------------------------------------------------
-spec finished(tls_version(), client | server, integer(), binary(), tls_handshake_history()) ->
#finished{}.
@@ -660,6 +689,57 @@ renegotiation_info(server, ConnectionStates, true) ->
#renegotiation_info{renegotiated_connection = undefined}
end.
+decode_next_protocols({next_protocol_negotiation, Protocols}) ->
+ decode_next_protocols(Protocols, []).
+decode_next_protocols(<<>>, Acc) ->
+ lists:reverse(Acc);
+decode_next_protocols(<<?BYTE(Len), Protocol:Len/binary, Rest/binary>>, Acc) ->
+ case Len of
+ 0 ->
+ {error, invalid_next_protocols};
+ _ ->
+ decode_next_protocols(Rest, [Protocol|Acc])
+ end;
+decode_next_protocols(_Bytes, _Acc) ->
+ {error, invalid_next_protocols}.
+
+next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) ->
+ NextProtocolSelector =/= undefined andalso not Renegotiating.
+
+handle_next_protocol_on_server(#client_hello{next_protocol_negotiation = undefined}, _Renegotiation, _SslOpts) ->
+ undefined;
+
+handle_next_protocol_on_server(#client_hello{next_protocol_negotiation = {next_protocol_negotiation, <<>>}},
+ false, #ssl_options{next_protocols_advertised = Protocols}) ->
+ Protocols;
+
+handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). % unexpected next protocol extension
+
+handle_next_protocol(#server_hello{next_protocol_negotiation = undefined},
+ _NextProtocolSelector, _Renegotiating) ->
+ undefined;
+
+handle_next_protocol(#server_hello{next_protocol_negotiation = Protocols},
+ NextProtocolSelector, Renegotiating) ->
+
+ case next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) of
+ true ->
+ select_next_protocol(decode_next_protocols(Protocols), NextProtocolSelector);
+ false ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) % unexpected next protocol extension
+ end.
+
+select_next_protocol({error, _Reason}, _NextProtocolSelector) ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+select_next_protocol(Protocols, NextProtocolSelector) ->
+ case NextProtocolSelector(Protocols) of
+ ?NO_PROTOCOL ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+ Protocol when is_binary(Protocol) ->
+ Protocol
+ end.
+
handle_renegotiation_info(_, #renegotiation_info{renegotiated_connection = ?byte(0)},
ConnectionStates, false, _, _) ->
{ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
@@ -816,17 +896,21 @@ master_secret(Version, MasterSecret, #security_parameters{
ServerCipherState, Role)}.
-dec_hs(_Version, ?HELLO_REQUEST, <<>>) ->
+dec_hs(_, ?NEXT_PROTOCOL, <<?BYTE(SelectedProtocolLength), SelectedProtocol:SelectedProtocolLength/binary,
+ ?BYTE(PaddingLength), _Padding:PaddingLength/binary>>) ->
+ #next_protocol{selected_protocol = SelectedProtocol};
+
+dec_hs(_, ?HELLO_REQUEST, <<>>) ->
#hello_request{};
%% Client hello v2.
%% The server must be able to receive such messages, from clients that
%% are willing to use ssl v3 or higher, but have ssl v2 compatibility.
dec_hs(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor),
- ?UINT16(CSLength), ?UINT16(0),
- ?UINT16(CDLength),
- CipherSuites:CSLength/binary,
- ChallengeData:CDLength/binary>>) ->
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ CipherSuites:CSLength/binary,
+ ChallengeData:CDLength/binary>>) ->
#client_hello{client_version = {Major, Minor},
random = ssl_ssl2:client_random(ChallengeData, CDLength),
session_id = 0,
@@ -839,20 +923,22 @@ dec_hs(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?UINT16(Cs_length), CipherSuites:Cs_length/binary,
?BYTE(Cm_length), Comp_methods:Cm_length/binary,
Extensions/binary>>) ->
- HelloExtensions = dec_hello_extensions(Extensions),
- RenegotiationInfo = proplists:get_value(renegotiation_info, HelloExtensions,
- undefined),
- HashSigns = proplists:get_value(hash_signs, HelloExtensions,
- undefined),
+
+ DecodedExtensions = dec_hello_extensions(Extensions),
+ RenegotiationInfo = proplists:get_value(renegotiation_info, DecodedExtensions, undefined),
+ HashSigns = proplists:get_value(hash_signs, DecodedExtensions, undefined),
+ NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, DecodedExtensions, undefined),
+
#client_hello{
- client_version = {Major,Minor},
- random = Random,
- session_id = Session_ID,
- cipher_suites = from_2bytes(CipherSuites),
- compression_methods = Comp_methods,
- renegotiation_info = RenegotiationInfo,
- hash_signs = HashSigns
- };
+ client_version = {Major,Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suites = from_2bytes(CipherSuites),
+ compression_methods = Comp_methods,
+ renegotiation_info = RenegotiationInfo,
+ hash_signs = HashSigns,
+ next_protocol_negotiation = NextProtocolNegotiation
+ };
dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
@@ -868,7 +954,7 @@ dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
- Cipher_suite:2/binary, ?BYTE(Comp_method),
+ Cipher_suite:2/binary, ?BYTE(Comp_method),
?UINT16(ExtLen), Extensions:ExtLen/binary>>) ->
HelloExtensions = dec_hello_extensions(Extensions, []),
@@ -876,6 +962,8 @@ dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
undefined),
HashSigns = proplists:get_value(hash_signs, HelloExtensions,
undefined),
+ NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, HelloExtensions, undefined),
+
#server_hello{
server_version = {Major,Minor},
random = Random,
@@ -883,7 +971,8 @@ dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
cipher_suite = Cipher_suite,
compression_method = Comp_method,
renegotiation_info = RenegotiationInfo,
- hash_signs = HashSigns};
+ hash_signs = HashSigns,
+ next_protocol_negotiation = NextProtocolNegotiation};
dec_hs(_Version, ?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>) ->
#certificate{asn1_certificates = certs_to_list(ASN1Certs)};
@@ -959,6 +1048,9 @@ dec_hello_extensions(_) ->
dec_hello_extensions(<<>>, Acc) ->
Acc;
+dec_hello_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc) ->
+ Prop = {next_protocol_negotiation, #next_protocol_negotiation{extension_data = ExtensionData}},
+ dec_hello_extensions(Rest, [Prop | Acc]);
dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binary, Rest/binary>>, Acc) ->
RenegotiateInfo = case Len of
1 -> % Initial handshake
@@ -982,6 +1074,7 @@ dec_hello_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
%% Ignore data following the ClientHello (i.e.,
%% extensions) if not understood.
+
dec_hello_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Acc) ->
dec_hello_extensions(Rest, Acc);
%% This theoretically should not happen if the protocol is followed, but if it does it is ignored.
@@ -1014,6 +1107,11 @@ certs_from_list(ACList) ->
<<?UINT24(CertLen), Cert/binary>>
end || Cert <- ACList]).
+enc_hs(#next_protocol{selected_protocol = SelectedProtocol}, _Version) ->
+ PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32),
+
+ {?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary,
+ ?BYTE(PaddingLength), 0:(PaddingLength * 8)>>};
enc_hs(#hello_request{}, _Version) ->
{?HELLO_REQUEST, <<>>};
enc_hs(#client_hello{client_version = {Major, Minor},
@@ -1022,19 +1120,21 @@ enc_hs(#client_hello{client_version = {Major, Minor},
cipher_suites = CipherSuites,
compression_methods = CompMethods,
renegotiation_info = RenegotiationInfo,
- hash_signs = HashSigns}, _Version) ->
+ hash_signs = HashSigns,
+ next_protocol_negotiation = NextProtocolNegotiation}, _Version) ->
SIDLength = byte_size(SessionID),
BinCompMethods = list_to_binary(CompMethods),
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
- Extensions0 = hello_extensions(RenegotiationInfo),
+ Extensions0 = hello_extensions(RenegotiationInfo, NextProtocolNegotiation),
Extensions1 = if
Major == 3, Minor >=3 -> Extensions0 ++ hello_extensions(HashSigns);
true -> Extensions0
end,
ExtensionsBin = enc_hello_extensions(Extensions1),
- {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+
+ {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
?UINT16(CsLength), BinCipherSuites/binary,
?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>};
@@ -1044,9 +1144,10 @@ 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,
+ next_protocol_negotiation = NextProtocolNegotiation}, _Version) ->
SID_length = byte_size(Session_ID),
- Extensions = hello_extensions(RenegotiationInfo),
+ Extensions = hello_extensions(RenegotiationInfo, NextProtocolNegotiation),
ExtensionsBin = enc_hello_extensions(Extensions),
{?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID/binary,
@@ -1119,8 +1220,9 @@ enc_sign(_HashSign, Sign, _Version) ->
SignLen = byte_size(Sign),
<<?UINT16(SignLen), Sign/binary>>.
-hello_extensions(undefined) ->
- [];
+hello_extensions(RenegotiationInfo, NextProtocolNegotiation) ->
+ hello_extensions(RenegotiationInfo) ++ next_protocol_extension(NextProtocolNegotiation).
+
%% Renegotiation info
hello_extensions(#renegotiation_info{renegotiated_connection = undefined}) ->
[];
@@ -1129,6 +1231,11 @@ hello_extensions(#renegotiation_info{} = Info) ->
hello_extensions(#hash_sign_algos{} = Info) ->
[Info].
+next_protocol_extension(undefined) ->
+ [];
+next_protocol_extension(#next_protocol_negotiation{} = Info) ->
+ [Info].
+
enc_hello_extensions(Extensions) ->
enc_hello_extensions(Extensions, <<>>).
enc_hello_extensions([], <<>>) ->
@@ -1137,6 +1244,9 @@ enc_hello_extensions([], Acc) ->
Size = byte_size(Acc),
<<?UINT16(Size), Acc/binary>>;
+enc_hello_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) ->
+ Len = byte_size(ExtensionData),
+ enc_hello_extensions(Rest, <<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), ExtensionData/binary, Acc/binary>>);
enc_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) ->
Len = byte_size(Info),
enc_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info/binary, Acc/binary>>);
@@ -1151,8 +1261,15 @@ enc_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest],
{Hash, Sign} <- HashSignAlgos >>,
ListLen = byte_size(SignAlgoList),
Len = ListLen + 2,
- enc_hello_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>).
+ enc_hello_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT),
+ ?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>).
+encode_client_protocol_negotiation(undefined, _) ->
+ undefined;
+encode_client_protocol_negotiation(_, false) ->
+ #next_protocol_negotiation{extension_data = <<>>};
+encode_client_protocol_negotiation(_, _) ->
+ undefined.
from_3bytes(Bin3) ->
from_3bytes(Bin3, []).
@@ -1284,6 +1401,7 @@ default_hash_signs() ->
[?TLSEXT_SIGALG(sha512),
?TLSEXT_SIGALG(sha384),
?TLSEXT_SIGALG(sha256),
+ ?TLSEXT_SIGALG(sha224),
?TLSEXT_SIGALG(sha),
?TLSEXT_SIGALG_DSA(sha),
?TLSEXT_SIGALG_RSA(md5)]}.
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index cc17dc2975..9af6511d68 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -33,6 +33,8 @@
-type public_key_info() :: {algo_oid(), #'RSAPublicKey'{} | integer() , public_key_params()}.
-type tls_handshake_history() :: {[binary()], [binary()]}.
+-define(NO_PROTOCOL, <<>>).
+
%% Signature algorithms
-define(ANON, 0).
-define(RSA, 1).
@@ -97,7 +99,8 @@
cipher_suites, % cipher_suites<2..2^16-1>
compression_methods, % compression_methods<1..2^8-1>,
renegotiation_info,
- hash_signs % supported combinations of hashes/signature algos
+ hash_signs, % supported combinations of hashes/signature algos
+ next_protocol_negotiation = undefined % [binary()]
}).
-record(server_hello, {
@@ -107,7 +110,8 @@
cipher_suite, % cipher_suites
compression_method, % compression_method
renegotiation_info,
- hash_signs % supported combinations of hashes/signature algos
+ hash_signs, % supported combinations of hashes/signature algos
+ next_protocol_negotiation = undefined % [binary()]
}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -234,6 +238,18 @@
hash_sign_algos
}).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Next Protocol Negotiation
+%% (http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-02)
+%% (http://technotes.googlecode.com/git/nextprotoneg.html)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(NEXTPROTONEG_EXT, 13172).
+-define(NEXT_PROTOCOL, 67).
+-record(next_protocol_negotiation, {extension_data}).
+
+-record(next_protocol, {selected_protocol}).
+
-endif. % -ifdef(ssl_handshake).
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index b8f2ae3b51..a5db2dcee7 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -106,7 +106,9 @@
% 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,
+ next_protocols_advertised = undefined, %% [binary()],
+ next_protocol_selector = undefined %% fun([binary()]) -> binary())
}).
-record(socket_options,
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index 0cf4f2ce33..13689ce7d8 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -24,8 +24,6 @@
-module(ssl_manager).
-behaviour(gen_server).
--include("ssl_internal.hrl").
-
%% Internal application API
-export([start_link/1, start_link_dist/1,
connection_init/2, cache_pem_file/2,
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index 2ad422fc03..a24b2d9444 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -72,15 +72,12 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) ->
server_id(Port, <<>>, _SslOpts, _Cert, _, _) ->
{ssl_manager:new_session_id(Port), undefined};
-server_id(Port, SuggestedId,
- #ssl_options{reuse_sessions = ReuseEnabled,
- reuse_session = ReuseFun},
- Cert, Cache, CacheCb) ->
+server_id(Port, SuggestedId, Options, Cert, Cache, CacheCb) ->
LifeTime = case application:get_env(ssl, session_lifetime) of
{ok, Time} when is_integer(Time) -> Time;
_ -> ?'24H_in_sec'
end,
- case is_resumable(SuggestedId, Port, ReuseEnabled,ReuseFun,
+ case is_resumable(SuggestedId, Port, Options,
Cache, CacheCb, LifeTime, Cert)
of
{true, Resumed} ->
@@ -112,9 +109,9 @@ select_session(Sessions, #ssl_options{ciphers = Ciphers}, OwnCert) ->
[[Id, _]|_] -> Id
end.
-is_resumable(_, _, false, _, _, _, _, _) ->
+is_resumable(_, _, #ssl_options{reuse_sessions = false}, _, _, _, _) ->
{false, undefined};
-is_resumable(SuggestedSessionId, Port, true, ReuseFun, Cache,
+is_resumable(SuggestedSessionId, Port, #ssl_options{reuse_session = ReuseFun} = Options, Cache,
CacheCb, SecondLifeTime, OwnCert) ->
case CacheCb:lookup(Cache, {Port, SuggestedSessionId}) of
#session{cipher_suite = CipherSuite,
@@ -125,6 +122,7 @@ is_resumable(SuggestedSessionId, Port, true, ReuseFun, Cache,
case resumable(IsResumable)
andalso (OwnCert == SessionOwnCert)
andalso valid_session(Session, SecondLifeTime)
+ andalso reusable_options(Options, Session)
andalso ReuseFun(SuggestedSessionId, PeerCert,
Compression, CipherSuite)
of
@@ -139,3 +137,9 @@ resumable(new) ->
false;
resumable(IsResumable) ->
IsResumable.
+
+reusable_options(#ssl_options{fail_if_no_peer_cert = true,
+ verify = verify_peer}, Session) ->
+ (Session#session.peer_certificate =/= undefined);
+reusable_options(_,_) ->
+ true.
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index 343157b22e..d36dcb588b 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -44,6 +44,8 @@ MODULES = \
ssl_to_openssl_SUITE \
ssl_session_cache_SUITE \
ssl_dist_SUITE \
+ ssl_npn_hello_SUITE \
+ ssl_npn_handshake_SUITE \
make_certs\
erl_make_certs
diff --git a/lib/ssl/test/erl_make_certs.erl b/lib/ssl/test/erl_make_certs.erl
index 254aa6d2f9..d6bdd05d01 100644
--- a/lib/ssl/test/erl_make_certs.erl
+++ b/lib/ssl/test/erl_make_certs.erl
@@ -137,10 +137,10 @@ decode_key(PemBin, Pw) ->
encode_key(Key = #'RSAPrivateKey'{}) ->
{ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key),
- {'RSAPrivateKey', list_to_binary(Der), not_encrypted};
+ {'RSAPrivateKey', Der, not_encrypted};
encode_key(Key = #'DSAPrivateKey'{}) ->
{ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key),
- {'DSAPrivateKey', list_to_binary(Der), not_encrypted}.
+ {'DSAPrivateKey', Der, not_encrypted}.
make_tbs(SubjectKey, Opts) ->
Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))),
diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl
index 693289990c..4603a9f846 100644
--- a/lib/ssl/test/make_certs.erl
+++ b/lib/ssl/test/make_certs.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2012. 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
@@ -121,7 +121,19 @@ create_self_signed_cert(Root, OpenSSLCmd, CAName, Cnf) ->
" -keyout ", KeyFile,
" -out ", CertFile],
Env = [{"ROOTDIR", Root}],
- cmd(Cmd, Env).
+ cmd(Cmd, Env),
+ fix_key_file(OpenSSLCmd, KeyFile).
+
+% openssl 1.0 generates key files in pkcs8 format by default and we don't handle this format
+fix_key_file(OpenSSLCmd, KeyFile) ->
+ KeyFileTmp = KeyFile ++ ".tmp",
+ Cmd = [OpenSSLCmd, " rsa",
+ " -in ",
+ KeyFile,
+ " -out ",
+ KeyFileTmp],
+ cmd(Cmd, []),
+ ok = file:rename(KeyFileTmp, KeyFile).
create_ca_dir(Root, CAName, Cnf) ->
CARoot = filename:join([Root, CAName]),
@@ -139,7 +151,8 @@ create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile) ->
" -keyout ", KeyFile,
" -out ", ReqFile],
Env = [{"ROOTDIR", Root}],
- cmd(Cmd, Env).
+ cmd(Cmd, Env),
+ fix_key_file(OpenSSLCmd, KeyFile).
sign_req(Root, OpenSSLCmd, CA, CertType, ReqFile, CertFile) ->
CACnfFile = filename:join([Root, CA, "ca.cnf"]),
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 6cf712fa6f..faed91e559 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -248,6 +248,7 @@ api_tests() ->
[connection_info,
peername,
peercert,
+ peercert_with_client_cert,
sockname,
versions,
controlling_process,
@@ -257,7 +258,10 @@ api_tests() ->
shutdown_write,
shutdown_both,
shutdown_error,
- hibernate
+ hibernate,
+ listen_socket,
+ ssl_accept_timeout,
+ ssl_recv_timeout
].
certificate_verify_tests() ->
@@ -273,6 +277,7 @@ certificate_verify_tests() ->
server_verify_client_once_passive,
server_verify_client_once_active,
server_verify_client_once_active_once,
+ new_server_wants_peer_cert,
client_verify_none_passive,
client_verify_none_active,
client_verify_none_active_once,
@@ -787,6 +792,43 @@ peercert(Config) when is_list(Config) ->
peercert_result(Socket) ->
ssl:peercert(Socket).
+%%--------------------------------------------------------------------
+
+peercert_with_client_cert(doc) ->
+ [""];
+peercert_with_client_cert(suite) ->
+ [];
+peercert_with_client_cert(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_dsa_opts, Config),
+ ServerOpts = ?config(server_dsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, peercert_result, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, peercert_result, []}},
+ {options, ClientOpts}]),
+
+ ServerCertFile = proplists:get_value(certfile, ServerOpts),
+ [{'Certificate', ServerBinCert, _}]= ssl_test_lib:pem_to_der(ServerCertFile),
+ ClientCertFile = proplists:get_value(certfile, ClientOpts),
+ [{'Certificate', ClientBinCert, _}]= ssl_test_lib:pem_to_der(ClientCertFile),
+
+ ServerMsg = {ok, ClientBinCert},
+ ClientMsg = {ok, ServerBinCert},
+
+ test_server:format("Testcase ~p, Client ~p Server ~p ~n",
+ [self(), Client, Server]),
+
+ ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
sockname(doc) ->
@@ -3609,9 +3651,14 @@ no_reuses_session_server_restart_new_cert(Config) when is_list(Config) ->
%% Make sure session is registered
test_server:sleep(?SLEEP),
+ Monitor = erlang:monitor(process, Server),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client0),
-
+ receive
+ {'DOWN', Monitor, _, _, _} ->
+ ok
+ end,
+
Server1 =
ssl_test_lib:start_server([{node, ServerNode}, {port, Port},
{from, self()},
@@ -3718,10 +3765,14 @@ reuseaddr(Config) when is_list(Config) ->
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
{options, [{active, false} | ClientOpts]}]),
- test_server:sleep(?SLEEP),
+ Monitor = erlang:monitor(process, Server),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client),
-
+ receive
+ {'DOWN', Monitor, _, _, _} ->
+ ok
+ end,
+
Server1 =
ssl_test_lib:start_server([{node, ServerNode}, {port, Port},
{from, self()},
@@ -3777,6 +3828,90 @@ hibernate(Config) ->
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
+listen_socket(doc) ->
+ ["Check error handling and inet compliance when calling API functions with listen sockets."];
+
+listen_socket(suite) ->
+ [];
+
+listen_socket(Config) ->
+ ServerOpts = ?config(server_opts, Config),
+ {ok, ListenSocket} = ssl:listen(0, ServerOpts),
+
+ %% This can be a valid thing to do as
+ %% options are inherited by the accept socket
+ ok = ssl:controlling_process(ListenSocket, self()),
+
+ {ok, _} = ssl:sockname(ListenSocket),
+
+ {error, enotconn} = ssl:send(ListenSocket, <<"data">>),
+ {error, enotconn} = ssl:recv(ListenSocket, 0),
+ {error, enotconn} = ssl:connection_info(ListenSocket),
+ {error, enotconn} = ssl:peername(ListenSocket),
+ {error, enotconn} = ssl:peercert(ListenSocket),
+ {error, enotconn} = ssl:session_info(ListenSocket),
+ {error, enotconn} = ssl:renegotiate(ListenSocket),
+ {error, enotconn} = ssl:prf(ListenSocket, 'master_secret', <<"Label">>, client_random, 256),
+ {error, enotconn} = ssl:shutdown(ListenSocket, read_write),
+
+ ok = ssl:close(ListenSocket).
+%%--------------------------------------------------------------------
+ssl_accept_timeout(doc) ->
+ ["Test ssl:ssl_accept timeout"];
+ssl_accept_timeout(suite) ->
+ [];
+ssl_accept_timeout(Config) ->
+ process_flag(trap_exit, true),
+ ServerOpts = ?config(server_opts, Config),
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {timeout, 5000},
+ {mfa, {ssl_test_lib,
+ no_result_msg, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ {ok, CSocket} = gen_tcp:connect(Hostname, Port, [binary, {active, true}]),
+
+ receive
+ {tcp_closed, CSocket} ->
+ ssl_test_lib:check_result(Server, {error, timeout}),
+ receive
+ {'EXIT', Server, _} ->
+ [] = supervisor:which_children(ssl_connection_sup)
+ end
+ end.
+
+%%--------------------------------------------------------------------
+ssl_recv_timeout(doc) ->
+ ["Test ssl:ssl_accept timeout"];
+ssl_recv_timeout(suite) ->
+ [];
+ssl_recv_timeout(Config) ->
+ ServerOpts = ?config(server_opts, Config),
+ ClientOpts = ?config(client_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_timeout_server, []}},
+ {options, [{active, false} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE,
+ send_recv_result_timeout_client, []}},
+ {options, [{active, false} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Client, ok, Server, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
connect_twice(doc) ->
[""];
@@ -4011,6 +4146,67 @@ client_server_opts({KeyAlgo,_,_}, Config) when KeyAlgo == dss orelse KeyAlgo ==
{?config(client_dsa_opts, Config),
?config(server_dsa_opts, Config)}.
+
+%%--------------------------------------------------------------------
+
+new_server_wants_peer_cert(doc) ->
+ ["Test that server configured to do client certification does"
+ " not reuse session without a client certificate."];
+new_server_wants_peer_cert(suite) ->
+ [];
+new_server_wants_peer_cert(Config) when is_list(Config) ->
+ ServerOpts = ?config(server_opts, Config),
+ VServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}
+ | ?config(server_verification_opts, Config)],
+ ClientOpts = ?config(client_verification_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, peercert_result, []}},
+ {options, [ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, ClientOpts}]),
+
+ Monitor = erlang:monitor(process, Server),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ receive
+ {'DOWN', Monitor, _, _, _} ->
+ ok
+ end,
+
+ Server1 = ssl_test_lib:start_server([{node, ServerNode}, {port, Port},
+ {from, self()},
+ {mfa, {?MODULE, peercert_result, []}},
+ {options, VServerOpts}]),
+ Client1 =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, [ClientOpts]}]),
+
+ CertFile = proplists:get_value(certfile, ClientOpts),
+ [{'Certificate', BinCert, _}]= ssl_test_lib:pem_to_der(CertFile),
+
+ ServerMsg = {error, no_peercert},
+ Sever1Msg = {ok, BinCert},
+
+ ssl_test_lib:check_result(Server, ServerMsg, Server1, Sever1Msg),
+
+ ssl_test_lib:close(Server1),
+ ssl_test_lib:close(Client),
+ ssl_test_lib:close(Client1).
+
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -4019,6 +4215,23 @@ send_recv_result(Socket) ->
{ok,"Hello world"} = ssl:recv(Socket, 11),
ok.
+send_recv_result_timeout_client(Socket) ->
+ {error, timeout} = ssl:recv(Socket, 11, 500),
+ ssl:send(Socket, "Hello world"),
+ receive
+ Msg ->
+ io:format("Msg ~p~n",[Msg])
+ after 500 ->
+ ok
+ end,
+ {ok, "Hello world"} = ssl:recv(Socket, 11, 500),
+ ok.
+send_recv_result_timeout_server(Socket) ->
+ ssl:send(Socket, "Hello"),
+ {ok, "Hello world"} = ssl:recv(Socket, 11),
+ ssl:send(Socket, " world"),
+ ok.
+
recv_close(Socket) ->
{error, closed} = ssl:recv(Socket, 11),
receive
diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_handshake_SUITE.erl
new file mode 100644
index 0000000000..8597aa6740
--- /dev/null
+++ b/lib/ssl/test/ssl_npn_handshake_SUITE.erl
@@ -0,0 +1,310 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(ssl_npn_handshake_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+-include_lib("common_test/include/ct.hrl").
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'},
+ {group, 'sslv3'}].
+
+groups() ->
+ [
+ {'tlsv1.2', [], next_protocol_tests()},
+ {'tlsv1.1', [], next_protocol_tests()},
+ {'tlsv1', [], next_protocol_tests()},
+ {'sslv3', [], next_protocol_not_supported()}
+ ].
+
+next_protocol_tests() ->
+ [validate_empty_protocols_are_not_allowed,
+ validate_empty_advertisement_list_is_allowed,
+ validate_advertisement_must_be_a_binary_list,
+ validate_client_protocols_must_be_a_tuple,
+ normal_npn_handshake_server_preference,
+ normal_npn_handshake_client_preference,
+ fallback_npn_handshake,
+ fallback_npn_handshake_server_preference,
+ client_negotiate_server_does_not_support,
+ no_client_negotiate_but_server_supports_npn,
+ renegotiate_from_client_after_npn_handshake
+ ].
+
+next_protocol_not_supported() ->
+ [npn_not_supported_client,
+ npn_not_supported_server
+ ].
+
+init_per_suite(Config) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ application:start(public_key),
+ ssl:start(),
+ Result =
+ (catch make_certs:all(?config(data_dir, Config),
+ ?config(priv_dir, Config))),
+ test_server:format("Make certs ~p~n", [Result]),
+ ssl_test_lib:cert_options(Config)
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ ssl:stop(),
+ application:stop(crypto).
+
+
+init_per_group(GroupName, Config) ->
+ case ssl_test_lib:is_tls_version(GroupName) of
+ true ->
+ case ssl_test_lib:sufficient_crypto_support(GroupName) of
+ true ->
+ ssl_test_lib:init_tls_version(GroupName),
+ Config;
+ false ->
+ {skip, "Missing crypto support"}
+ end;
+ _ ->
+ ssl:start(),
+ Config
+ end.
+
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+
+validate_empty_protocols_are_not_allowed(Config) when is_list(Config) ->
+ {error, {eoptions, {next_protocols_advertised, {invalid_protocol, <<>>}}}}
+ = (catch ssl:listen(9443,
+ [{next_protocols_advertised, [<<"foo/1">>, <<"">>]}])),
+ {error, {eoptions, {client_preferred_next_protocols, {invalid_protocol, <<>>}}}}
+ = (catch ssl:connect({127,0,0,1}, 9443,
+ [{client_preferred_next_protocols,
+ {client, [<<"foo/1">>, <<"">>], <<"foox/1">>}}], infinity)),
+ Option = {client_preferred_next_protocols, {invalid_protocol, <<"">>}},
+ {error, {eoptions, Option}} = (catch ssl:connect({127,0,0,1}, 9443, [Option], infinity)).
+
+%--------------------------------------------------------------------------------
+
+validate_empty_advertisement_list_is_allowed(Config) when is_list(Config) ->
+ Option = {next_protocols_advertised, []},
+ {ok, Socket} = ssl:listen(0, [Option]),
+ ssl:close(Socket).
+%--------------------------------------------------------------------------------
+
+validate_advertisement_must_be_a_binary_list(Config) when is_list(Config) ->
+ Option = {next_protocols_advertised, blah},
+ {error, {eoptions, Option}} = (catch ssl:listen(9443, [Option])).
+%--------------------------------------------------------------------------------
+
+validate_client_protocols_must_be_a_tuple(Config) when is_list(Config) ->
+ Option = {client_preferred_next_protocols, [<<"foo/1">>]},
+ {error, {eoptions, Option}} = (catch ssl:connect({127,0,0,1}, 9443, [Option])).
+
+%--------------------------------------------------------------------------------
+
+normal_npn_handshake_server_preference(Config) when is_list(Config) ->
+ run_npn_handshake(Config,
+ [{client_preferred_next_protocols,
+ {server, [<<"http/1.0">>, <<"http/1.1">>], <<"http/1.1">>}}],
+ [{next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}],
+ {ok, <<"http/1.1">>}).
+%--------------------------------------------------------------------------------
+
+normal_npn_handshake_client_preference(Config) when is_list(Config) ->
+ run_npn_handshake(Config,
+ [{client_preferred_next_protocols,
+ {client, [<<"http/1.0">>, <<"http/1.1">>], <<"http/1.1">>}}],
+ [{next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}],
+ {ok, <<"http/1.0">>}).
+
+%--------------------------------------------------------------------------------
+
+fallback_npn_handshake(Config) when is_list(Config) ->
+ run_npn_handshake(Config,
+ [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}}],
+ [{next_protocols_advertised, [<<"spdy/1">>, <<"http/1.1">>, <<"http/1.0">>]}],
+ {ok, <<"http/1.1">>}).
+%--------------------------------------------------------------------------------
+
+fallback_npn_handshake_server_preference(Config) when is_list(Config) ->
+ run_npn_handshake(Config,
+ [{client_preferred_next_protocols, {server, [<<"spdy/2">>], <<"http/1.1">>}}],
+ [{next_protocols_advertised, [<<"spdy/1">>, <<"http/1.1">>, <<"http/1.0">>]}],
+ {ok, <<"http/1.1">>}).
+
+%--------------------------------------------------------------------------------
+
+no_client_negotiate_but_server_supports_npn(Config) when is_list(Config) ->
+ run_npn_handshake(Config,
+ [],
+ [{next_protocols_advertised, [<<"spdy/1">>, <<"http/1.1">>, <<"http/1.0">>]}],
+ {error, next_protocol_not_negotiated}).
+%--------------------------------------------------------------------------------
+
+
+client_negotiate_server_does_not_support(Config) when is_list(Config) ->
+ run_npn_handshake(Config,
+ [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}}],
+ [],
+ {error, next_protocol_not_negotiated}).
+
+%--------------------------------------------------------------------------------
+renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) ->
+ Data = "hello world",
+
+ ClientOpts0 = ?config(client_opts, Config),
+ ClientOpts = [{client_preferred_next_protocols,
+ {client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0,
+ ServerOpts0 = ?config(server_opts, Config),
+ ServerOpts = [{next_protocols_advertised,
+ [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0,
+ ExpectedProtocol = {ok, <<"http/1.0">>},
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, ssl_receive_and_assert_npn, [ExpectedProtocol, Data]}},
+ {options, ServerOpts}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, assert_npn_and_renegotiate_and_send_data, [ExpectedProtocol, Data]}},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok).
+
+%--------------------------------------------------------------------------------
+npn_not_supported_client(Config) when is_list(Config) ->
+ ClientOpts0 = ?config(client_opts, Config),
+ PrefProtocols = {client_preferred_next_protocols,
+ {client, [<<"http/1.0">>], <<"http/1.1">>}},
+ ClientOpts = [PrefProtocols] ++ ClientOpts0,
+ {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Client = ssl_test_lib:start_client_error([{node, ClientNode},
+ {port, 8888}, {host, Hostname},
+ {from, self()}, {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Client, {error,
+ {eoptions,
+ {not_supported_in_sslv3, PrefProtocols}}}).
+
+%--------------------------------------------------------------------------------
+npn_not_supported_server(Config) when is_list(Config)->
+ ServerOpts0 = ?config(server_opts, Config),
+ AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]},
+ ServerOpts = [AdvProtocols] ++ ServerOpts0,
+
+ {error, {eoptions, {not_supported_in_sslv3, AdvProtocols}}} = ssl:listen(0, ServerOpts).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) ->
+ Data = "hello world",
+
+ ClientOpts0 = ?config(client_opts, Config),
+ ClientOpts = ClientExtraOpts ++ ClientOpts0,
+ ServerOpts0 = ?config(server_opts, Config),
+ ServerOpts = ServerExtraOpts ++ ServerOpts0,
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, ssl_receive_and_assert_npn, [ExpectedProtocol, Data]}},
+ {options, ServerOpts}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, ssl_send_and_assert_npn, [ExpectedProtocol, Data]}},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok).
+
+
+assert_npn(Socket, Protocol) ->
+ test_server:format("Negotiated Protocol ~p, Expecting: ~p ~n",
+ [ssl:negotiated_next_protocol(Socket), Protocol]),
+ Protocol = ssl:negotiated_next_protocol(Socket).
+
+assert_npn_and_renegotiate_and_send_data(Socket, Protocol, Data) ->
+ assert_npn(Socket, Protocol),
+ test_server:format("Renegotiating ~n", []),
+ ok = ssl:renegotiate(Socket),
+ ssl:send(Socket, Data),
+ assert_npn(Socket, Protocol),
+ ok.
+
+ssl_send_and_assert_npn(Socket, Protocol, Data) ->
+ assert_npn(Socket, Protocol),
+ ssl_send(Socket, Data).
+
+ssl_receive_and_assert_npn(Socket, Protocol, Data) ->
+ assert_npn(Socket, Protocol),
+ ssl_receive(Socket, Data).
+
+ssl_send(Socket, Data) ->
+ test_server:format("Connection info: ~p~n",
+ [ssl:connection_info(Socket)]),
+ ssl:send(Socket, Data).
+
+ssl_receive(Socket, Data) ->
+ ssl_receive(Socket, Data, []).
+
+ssl_receive(Socket, Data, Buffer) ->
+ test_server:format("Connection info: ~p~n",
+ [ssl:connection_info(Socket)]),
+ receive
+ {ssl, Socket, MoreData} ->
+ test_server:format("Received ~p~n",[MoreData]),
+ NewBuffer = Buffer ++ MoreData,
+ case NewBuffer of
+ Data ->
+ ssl:send(Socket, "Got it"),
+ ok;
+ _ ->
+ ssl_receive(Socket, Data, NewBuffer)
+ end;
+ Other ->
+ test_server:fail({unexpected_message, Other})
+ after 4000 ->
+ test_server:fail({did_not_get, Data})
+ end.
+
+
+connection_info_result(Socket) ->
+ ssl:connection_info(Socket).
diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl
new file mode 100644
index 0000000000..5102c74e87
--- /dev/null
+++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl
@@ -0,0 +1,117 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssl_npn_hello_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+-include("ssl_handshake.hrl").
+-include("ssl_record.hrl").
+-include_lib("common_test/include/ct.hrl").
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [encode_and_decode_npn_client_hello_test,
+ encode_and_decode_npn_server_hello_test,
+ encode_and_decode_client_hello_test,
+ encode_and_decode_server_hello_test,
+ create_server_hello_with_advertised_protocols_test,
+ create_server_hello_with_no_advertised_protocols_test].
+
+
+create_client_handshake(Npn) ->
+ ssl_handshake:encode_handshake(#client_hello{
+ client_version = {1, 2},
+ random = <<1:256>>,
+ session_id = <<>>,
+ cipher_suites = "",
+ compression_methods = "",
+ next_protocol_negotiation = Npn,
+ renegotiation_info = #renegotiation_info{}
+ }, vsn).
+
+
+encode_and_decode_client_hello_test(_Config) ->
+ HandShakeData = create_client_handshake(undefined),
+ Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+ {[{DecodedHandshakeMessage, _Raw}], _} = ssl_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>),
+ NextProtocolNegotiation = DecodedHandshakeMessage#client_hello.next_protocol_negotiation,
+ NextProtocolNegotiation = undefined.
+
+encode_and_decode_npn_client_hello_test(_Config) ->
+ HandShakeData = create_client_handshake(#next_protocol_negotiation{extension_data = <<>>}),
+ Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+ {[{DecodedHandshakeMessage, _Raw}], _} = ssl_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>),
+ NextProtocolNegotiation = DecodedHandshakeMessage#client_hello.next_protocol_negotiation,
+ NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<>>}.
+
+create_server_handshake(Npn) ->
+ ssl_handshake:encode_handshake(#server_hello{
+ server_version = {1, 2},
+ random = <<1:256>>,
+ session_id = <<>>,
+ cipher_suite = <<1,2>>,
+ compression_method = 1,
+ next_protocol_negotiation = Npn,
+ renegotiation_info = #renegotiation_info{}
+ }, vsn).
+
+encode_and_decode_server_hello_test(_Config) ->
+ HandShakeData = create_server_handshake(undefined),
+ Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+ {[{DecodedHandshakeMessage, _Raw}], _} =
+ ssl_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>),
+ NextProtocolNegotiation = DecodedHandshakeMessage#server_hello.next_protocol_negotiation,
+ NextProtocolNegotiation = undefined.
+
+encode_and_decode_npn_server_hello_test(_Config) ->
+ HandShakeData = create_server_handshake(#next_protocol_negotiation{extension_data = <<6, "spdy/2">>}),
+ Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+ {[{DecodedHandshakeMessage, _Raw}], _} = ssl_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>),
+ NextProtocolNegotiation = DecodedHandshakeMessage#server_hello.next_protocol_negotiation,
+ ct:print("~p ~n", [NextProtocolNegotiation]),
+ NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<6, "spdy/2">>}.
+
+create_connection_states() ->
+ #connection_states{
+ pending_read = #connection_state{
+ security_parameters = #security_parameters{
+ server_random = <<1:256>>,
+ compression_algorithm = 1,
+ cipher_suite = <<1, 2>>
+ }
+ },
+
+ current_read = #connection_state {
+ secure_renegotiation = false
+ }
+ }.
+
+create_server_hello_with_no_advertised_protocols_test(_Config) ->
+ Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), false, undefined),
+ undefined = Hello#server_hello.next_protocol_negotiation.
+
+create_server_hello_with_advertised_protocols_test(_Config) ->
+ Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(),
+ false, [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]),
+ #next_protocol_negotiation{extension_data = <<6, "spdy/1", 8, "http/1.0", 8, "http/1.1">>} =
+ Hello#server_hello.next_protocol_negotiation.
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 63731ee25c..f1f5b9ae0a 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -72,7 +72,13 @@ run_server(Opts) ->
run_server(ListenSocket, Opts).
run_server(ListenSocket, Opts) ->
- AcceptSocket = connect(ListenSocket, Opts),
+ do_run_server(ListenSocket, connect(ListenSocket, Opts), Opts).
+
+do_run_server(_, {error, timeout} = Result, Opts) ->
+ Pid = proplists:get_value(from, Opts),
+ Pid ! {self(), Result};
+
+do_run_server(ListenSocket, AcceptSocket, Opts) ->
Node = proplists:get_value(node, Opts),
Pid = proplists:get_value(from, Opts),
{Module, Function, Args} = proplists:get_value(mfa, Opts),
@@ -102,7 +108,8 @@ run_server(ListenSocket, Opts) ->
connect(ListenSocket, Opts) ->
Node = proplists:get_value(node, Opts),
ReconnectTimes = proplists:get_value(reconnect_times, Opts, 0),
- AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy),
+ Timeout = proplists:get_value(timeout, Opts, infinity),
+ AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy, Timeout),
case ReconnectTimes of
0 ->
AcceptSocket;
@@ -111,15 +118,21 @@ connect(ListenSocket, Opts) ->
AcceptSocket
end.
-connect(_, _, 0, AcceptSocket) ->
+connect(_, _, 0, AcceptSocket, _) ->
AcceptSocket;
-connect(ListenSocket, Node, N, _) ->
+connect(ListenSocket, Node, N, _, Timeout) ->
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).
+ test_server:format("ssl:ssl_accept(~p, ~p)~n", [AcceptSocket, Timeout]),
+
+ case rpc:call(Node, ssl, ssl_accept, [AcceptSocket, Timeout]) of
+ ok ->
+ connect(ListenSocket, Node, N-1, AcceptSocket, Timeout);
+ Result ->
+ Result
+ end.
+
remove_close_msg(0) ->
ok;
diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl
index d446014f7b..f4e19b3f87 100644
--- a/lib/ssl/test/ssl_to_openssl_SUITE.erl
+++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl
@@ -29,7 +29,7 @@
-define(TIMEOUT, 120000).
-define(LONG_TIMEOUT, 600000).
-define(SLEEP, 1000).
--define(OPENSSL_RENEGOTIATE, "r\n").
+-define(OPENSSL_RENEGOTIATE, "R\n").
-define(OPENSSL_QUIT, "Q\n").
-define(OPENSSL_GARBAGE, "P\n").
-define(EXPIRE, 10).
@@ -114,6 +114,17 @@ special_init(TestCase, Config)
special_init(ssl2_erlang_server_openssl_client, Config) ->
check_sane_openssl_sslv2(Config);
+special_init(TestCase, Config)
+ when TestCase == erlang_client_openssl_server_npn;
+ TestCase == erlang_server_openssl_client_npn;
+ TestCase == erlang_server_openssl_client_npn_renegotiate;
+ TestCase == erlang_client_openssl_server_npn_renegotiate;
+ TestCase == erlang_server_openssl_client_npn_only_server;
+ TestCase == erlang_server_openssl_client_npn_only_client;
+ TestCase == erlang_client_openssl_server_npn_only_client;
+ TestCase == erlang_client_openssl_server_npn_only_server ->
+ check_openssl_npn_support(Config);
+
special_init(_, Config) ->
Config.
@@ -161,9 +172,9 @@ all() ->
groups() ->
[{basic, [], basic_tests()},
- {'tlsv1.2', [], all_versions_tests()},
- {'tlsv1.1', [], all_versions_tests()},
- {'tlsv1', [], all_versions_tests()},
+ {'tlsv1.2', [], all_versions_tests() ++ npn_tests()},
+ {'tlsv1.1', [], all_versions_tests() ++ npn_tests()},
+ {'tlsv1', [], all_versions_tests()++ npn_tests()},
{'sslv3', [], all_versions_tests()}].
basic_tests() ->
@@ -179,16 +190,26 @@ all_versions_tests() ->
erlang_server_openssl_client_dsa_cert,
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_nowrap_seqnum,
+ erlang_server_openssl_client_nowrap_seqnum,
erlang_client_openssl_server_no_server_ca_cert,
erlang_client_openssl_server_client_cert,
erlang_server_openssl_client_client_cert,
ciphers_rsa_signed_certs,
ciphers_dsa_signed_certs,
erlang_client_bad_openssl_server,
- ssl2_erlang_server_openssl_client
- ].
+ expired_session,
+ ssl2_erlang_server_openssl_client].
+
+npn_tests() ->
+ [erlang_client_openssl_server_npn,
+ erlang_server_openssl_client_npn,
+ erlang_server_openssl_client_npn_renegotiate,
+ erlang_client_openssl_server_npn_renegotiate,
+ erlang_server_openssl_client_npn_only_client,
+ erlang_server_openssl_client_npn_only_server,
+ erlang_client_openssl_server_npn_only_client,
+ erlang_client_openssl_server_npn_only_server].
init_per_group(GroupName, Config) ->
case ssl_test_lib:is_tls_version(GroupName) of
@@ -544,14 +565,14 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
-erlang_client_openssl_server_no_wrap_sequence_number(doc) ->
+erlang_client_openssl_server_nowrap_seqnum(doc) ->
["Test that erlang client will renegotiate session when",
"max sequence number celing is about to be reached. Although"
"in the testcase we use the test option renegotiate_at"
" to lower treashold substantially."];
-erlang_client_openssl_server_no_wrap_sequence_number(suite) ->
+erlang_client_openssl_server_nowrap_seqnum(suite) ->
[];
-erlang_client_openssl_server_no_wrap_sequence_number(Config) when is_list(Config) ->
+erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) ->
process_flag(trap_exit, true),
ServerOpts = ?config(server_opts, Config),
ClientOpts = ?config(client_opts, Config),
@@ -590,15 +611,15 @@ erlang_client_openssl_server_no_wrap_sequence_number(Config) when is_list(Config
process_flag(trap_exit, false),
ok.
%%--------------------------------------------------------------------
-erlang_server_openssl_client_no_wrap_sequence_number(doc) ->
+erlang_server_openssl_client_nowrap_seqnum(doc) ->
["Test that erlang client will renegotiate session when",
"max sequence number celing is about to be reached. Although"
"in the testcase we use the test option renegotiate_at"
" to lower treashold substantially."];
-erlang_server_openssl_client_no_wrap_sequence_number(suite) ->
+erlang_server_openssl_client_nowrap_seqnum(suite) ->
[];
-erlang_server_openssl_client_no_wrap_sequence_number(Config) when is_list(Config) ->
+erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) ->
process_flag(trap_exit, true),
ServerOpts = ?config(server_opts, Config),
@@ -1059,16 +1080,257 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) ->
OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
port_command(OpenSslPort, Data),
-
+ receive
+ {'EXIT', OpenSslPort, _} ->
+ ok
+
+ end,
ssl_test_lib:check_result(Server, {error,"protocol version"}),
-
+ process_flag(trap_exit, false).
+
+%%--------------------------------------------------------------------
+erlang_client_openssl_server_npn(doc) ->
+ ["Test erlang client with openssl server doing npn negotiation"];
+erlang_client_openssl_server_npn(suite) ->
+ [];
+erlang_client_openssl_server_npn(Config) when is_list(Config) ->
+ Data = "From openssl to erlang",
+ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, fun(Client, OpensslPort) ->
+ port_command(OpensslPort, Data),
+
+ ssl_test_lib:check_result(Client, ok)
+ end),
+
+ ok.
+
+
+%%--------------------------------------------------------------------
+erlang_client_openssl_server_npn_renegotiate(doc) ->
+ ["Test erlang client with openssl server doing npn negotiation and renegotiate"];
+erlang_client_openssl_server_npn_renegotiate(suite) ->
+ [];
+erlang_client_openssl_server_npn_renegotiate(Config) when is_list(Config) ->
+ Data = "From openssl to erlang",
+ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, fun(Client, OpensslPort) ->
+ port_command(OpensslPort, ?OPENSSL_RENEGOTIATE),
+ test_server:sleep(?SLEEP),
+ port_command(OpensslPort, Data),
+ ssl_test_lib:check_result(Client, ok)
+ end),
+ ok.
+
+
+%%--------------------------------------------------------------------------
+
+
+erlang_server_openssl_client_npn(doc) ->
+ ["Test erlang server with openssl client and npn negotiation"];
+erlang_server_openssl_client_npn(suite) ->
+ [];
+erlang_server_openssl_client_npn(Config) when is_list(Config) ->
+
+ Data = "From openssl to erlang",
+ start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, fun(Server, OpensslPort) ->
+ port_command(OpensslPort, Data),
+ ssl_test_lib:check_result(Server, ok)
+ end),
+ ok.
+
+%%--------------------------------------------------------------------------
+
+erlang_server_openssl_client_npn_renegotiate(doc) ->
+ ["Test erlang server with openssl client and npn negotiation with renegotiation"];
+erlang_server_openssl_client_npn_renegotiate(suite) ->
+ [];
+erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) ->
+ Data = "From openssl to erlang",
+ start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, fun(Server, OpensslPort) ->
+ port_command(OpensslPort, ?OPENSSL_RENEGOTIATE),
+ test_server:sleep(?SLEEP),
+ port_command(OpensslPort, Data),
+ ssl_test_lib:check_result(Server, ok)
+ end),
+ ok.
+%%--------------------------------------------------------------------------
+
+erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) ->
+ Data = "From openssl to erlang",
+ start_erlang_client_and_openssl_server_with_opts(Config, [], "-nextprotoneg spdy/2", Data, fun(Server, OpensslPort) ->
+ port_command(OpensslPort, Data),
+ ssl_test_lib:check_result(Server, ok)
+ end),
+ ok.
+
+%%--------------------------------------------------------------------------
+
+erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) ->
+ Data = "From openssl to erlang",
+ start_erlang_client_and_openssl_server_with_opts(Config, [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}}], "", Data, fun(Server, OpensslPort) ->
+ port_command(OpensslPort, Data),
+ ssl_test_lib:check_result(Server, ok)
+ end),
+ ok.
+
+%%--------------------------------------------------------------------------
+erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) ->
+ Data = "From openssl to erlang",
+ start_erlang_server_and_openssl_client_with_opts(Config, [{next_protocols_advertised, [<<"spdy/2">>]}], "", Data, fun(Server, OpensslPort) ->
+ port_command(OpensslPort, Data),
+ ssl_test_lib:check_result(Server, ok)
+ end),
+ ok.
+
+erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) ->
+ Data = "From openssl to erlang",
+ start_erlang_server_and_openssl_client_with_opts(Config, [], "-nextprotoneg spdy/2", Data, fun(Server, OpensslPort) ->
+ port_command(OpensslPort, Data),
+ ssl_test_lib:check_result(Server, ok)
+ end),
+ ok.
+
+%%--------------------------------------------------------------------------
+
+start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) ->
+ process_flag(trap_exit, true),
+ ServerOpts = ?config(server_opts, Config),
+ ClientOpts0 = ?config(client_opts, Config),
+ ClientOpts = ErlangClientOpts ++ ClientOpts0,
+
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = "From openssl to erlang",
+
+ Port = ssl_test_lib:inet_port(node()),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+ Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+
+ Cmd = "openssl s_server " ++ OpensslServerOpts ++ " -accept " ++
+ integer_to_list(Port) ++ version_flag(Version) ++
+ " -cert " ++ CertFile ++ " -key " ++ KeyFile,
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+
+ wait_for_openssl_server(),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE,
+ erlang_ssl_receive, [Data]}},
+ {options, ClientOpts}]),
+
+ Callback(Client, OpensslPort),
+
+ %% Clean close down! Server needs to be closed first !!
+ close_port(OpensslPort),
+
+ ssl_test_lib:close(Client),
+ process_flag(trap_exit, false).
+
+start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) ->
+ process_flag(trap_exit, true),
+ ServerOpts = ?config(server_opts, Config),
+ ClientOpts0 = ?config(client_opts, Config),
+ ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0],
+
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = "From openssl to erlang",
+
+ Port = ssl_test_lib:inet_port(node()),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+ Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+
+ Cmd = "openssl s_server -msg -nextprotoneg http/1.1,spdy/2 -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++
+ " -cert " ++ CertFile ++ " -key " ++ KeyFile,
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+
+ wait_for_openssl_server(),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE,
+ erlang_ssl_receive_and_assert_npn, [<<"spdy/2">>, Data]}},
+ {options, ClientOpts}]),
+
+ Callback(Client, OpensslPort),
+
%% Clean close down! Server needs to be closed first !!
+ close_port(OpensslPort),
+
+ ssl_test_lib:close(Client),
+ process_flag(trap_exit, false).
+
+start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callback) ->
+ process_flag(trap_exit, true),
+ ServerOpts0 = ?config(server_opts, Config),
+ ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]}, ServerOpts0],
+
+ {_, ServerNode, _} = ssl_test_lib:run_where(Config),
+
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, erlang_ssl_receive_and_assert_npn, [<<"spdy/2">>, Data]}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+ Cmd = "openssl s_client -nextprotoneg http/1.0,spdy/2 -msg -port " ++ integer_to_list(Port) ++ version_flag(Version) ++
+ " -host localhost",
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+
+ Callback(Server, OpenSslPort),
+
ssl_test_lib:close(Server),
+
close_port(OpenSslPort),
- process_flag(trap_exit, false),
- ok.
+ process_flag(trap_exit, false).
-%%--------------------------------------------------------------------
+start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenSSLClientOpts, Data, Callback) ->
+ process_flag(trap_exit, true),
+ ServerOpts0 = ?config(server_opts, Config),
+ ServerOpts = ErlangServerOpts ++ ServerOpts0,
+
+ {_, ServerNode, _} = ssl_test_lib:run_where(Config),
+
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, erlang_ssl_receive, [Data]}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Cmd = "openssl s_client " ++ OpenSSLClientOpts ++ " -msg -port " ++ integer_to_list(Port) ++
+ " -host localhost",
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+
+ Callback(Server, OpenSslPort),
+
+ ssl_test_lib:close(Server),
+
+ close_port(OpenSslPort),
+ process_flag(trap_exit, false).
+
+
+erlang_ssl_receive_and_assert_npn(Socket, Protocol, Data) ->
+ {ok, Protocol} = ssl:negotiated_next_protocol(Socket),
+ erlang_ssl_receive(Socket, Data),
+ {ok, Protocol} = ssl:negotiated_next_protocol(Socket),
+ ok.
erlang_ssl_receive(Socket, Data) ->
test_server:format("Connection info: ~p~n",
@@ -1168,6 +1430,15 @@ version_flag('tlsv1.2') ->
version_flag(sslv3) ->
" -ssl3 ".
+check_openssl_npn_support(Config) ->
+ HelpText = os:cmd("openssl s_client --help"),
+ case string:str(HelpText, "nextprotoneg") of
+ 0 ->
+ {skip, "Openssl not compiled with nextprotoneg support"};
+ _ ->
+ Config
+ end.
+
check_sane_openssl_renegotaite(Config) ->
case os:cmd("openssl version") of
"OpenSSL 0.9.8" ++ _ ->
@@ -1179,11 +1450,27 @@ check_sane_openssl_renegotaite(Config) ->
end.
check_sane_openssl_sslv2(Config) ->
- case os:cmd("openssl version") of
- "OpenSSL 1." ++ _ ->
- {skip, "sslv2 by default turned of in 1.*"};
- _ ->
- Config
+ Port = open_port({spawn, "openssl s_client -ssl2 "}, [stderr_to_stdout]),
+ case supports_sslv2(Port) of
+ true ->
+ Config;
+ false ->
+ {skip, "sslv2 not supported by openssl"}
+ end.
+
+supports_sslv2(Port) ->
+ receive
+ {Port, {data, "unknown option -ssl2" ++ _}} ->
+ false;
+ {Port, {data, Data}} ->
+ case lists:member("error", string:tokens(Data, ":")) of
+ true ->
+ false;
+ false ->
+ supports_sslv2(Port)
+ end
+ after 500 ->
+ true
end.
check_sane_openssl_version(Version) ->
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index bc8b8fd039..adfb29e639 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 5.1.1
+SSL_VSN = 5.1.2