aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/test/ssl_alpn_handshake_SUITE.erl
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2015-02-26 18:55:16 +0100
committerZandra Hird <[email protected]>2015-03-17 11:55:53 +0100
commit7cf85926c11d38ce5ebd181f9f98185f359d64a3 (patch)
treee2eef9483f32c5616ebe0c5cf91fcb06e0944d87 /lib/ssl/test/ssl_alpn_handshake_SUITE.erl
parente0c0518a14b747cf02a0e5adfbd5b116a189422d (diff)
downloadotp-7cf85926c11d38ce5ebd181f9f98185f359d64a3.tar.gz
otp-7cf85926c11d38ce5ebd181f9f98185f359d64a3.tar.bz2
otp-7cf85926c11d38ce5ebd181f9f98185f359d64a3.zip
ssl: Add TLS-ALPN support
This commit adds support for RFC7301, application-layer protocol negotiation. ALPN is the standard based approach to the NPN extension, and is required for HTTP/2. ALPN lives side by side with NPN and provides an equivalent feature but in this case it is the server that decides what protocol to use, not the client. When both ALPN and NPN are sent by a client, and the server is configured with both ALPN and NPN options, ALPN will always take precedence. This behavior can also be found in the OpenSSL implementation of ALPN. ALPN and NPN share the ssl:negotiated_protocol/1 function for retrieving the negotiated protocol. The previously existing function ssl:negotiated_next_protocol/1 still exists, but has been deprecated and removed from the documentation. The tests against OpenSSL require OpenSSL version 1.0.2+.
Diffstat (limited to 'lib/ssl/test/ssl_alpn_handshake_SUITE.erl')
-rw-r--r--lib/ssl/test/ssl_alpn_handshake_SUITE.erl414
1 files changed, 414 insertions, 0 deletions
diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl
new file mode 100644
index 0000000000..ccd70fa605
--- /dev/null
+++ b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl
@@ -0,0 +1,414 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2015. 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_alpn_handshake_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+-include_lib("common_test/include/ct.hrl").
+
+-define(SLEEP, 500).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'},
+ {group, 'sslv3'}].
+
+groups() ->
+ [
+ {'tlsv1.2', [], alpn_tests()},
+ {'tlsv1.1', [], alpn_tests()},
+ {'tlsv1', [], alpn_tests()},
+ {'sslv3', [], alpn_not_supported()}
+ ].
+
+alpn_tests() ->
+ [empty_protocols_are_not_allowed,
+ protocols_must_be_a_binary_list,
+ empty_client,
+ empty_server,
+ empty_client_empty_server,
+ no_matching_protocol,
+ client_alpn_and_server_alpn,
+ client_alpn_and_server_no_support,
+ client_no_support_and_server_alpn,
+ client_alpn_npn_and_server_alpn,
+ client_alpn_npn_and_server_alpn_npn,
+ client_alpn_and_server_alpn_npn,
+ client_renegotiate,
+ session_reused
+ ].
+
+alpn_not_supported() ->
+ [alpn_not_supported_client,
+ alpn_not_supported_server
+ ].
+
+init_per_suite(Config) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl:start(),
+ Result =
+ (catch make_certs:all(?config(data_dir, Config),
+ ?config(priv_dir, Config))),
+ ct:log("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:unload(ssl),
+ 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 --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+empty_protocols_are_not_allowed(Config) when is_list(Config) ->
+ {error, {options, {alpn_preferred_protocols, {invalid_protocol, <<>>}}}}
+ = (catch ssl:listen(9443,
+ [{alpn_preferred_protocols, [<<"foo/1">>, <<"">>]}])),
+ {error, {options, {alpn_advertised_protocols, {invalid_protocol, <<>>}}}}
+ = (catch ssl:connect({127,0,0,1}, 9443,
+ [{alpn_advertised_protocols, [<<"foo/1">>, <<"">>]}])).
+
+%--------------------------------------------------------------------------------
+
+protocols_must_be_a_binary_list(Config) when is_list(Config) ->
+ Option1 = {alpn_preferred_protocols, hello},
+ {error, {options, Option1}} = (catch ssl:listen(9443, [Option1])),
+ Option2 = {alpn_preferred_protocols, [<<"foo/1">>, hello]},
+ {error, {options, {alpn_preferred_protocols, {invalid_protocol, hello}}}}
+ = (catch ssl:listen(9443, [Option2])),
+ Option3 = {alpn_advertised_protocols, hello},
+ {error, {options, Option3}} = (catch ssl:connect({127,0,0,1}, 9443, [Option3])),
+ Option4 = {alpn_advertised_protocols, [<<"foo/1">>, hello]},
+ {error, {options, {alpn_advertised_protocols, {invalid_protocol, hello}}}}
+ = (catch ssl:connect({127,0,0,1}, 9443, [Option4])).
+
+%--------------------------------------------------------------------------------
+
+empty_client(Config) when is_list(Config) ->
+ run_failing_handshake(Config,
+ [{alpn_advertised_protocols, []}],
+ [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}],
+ {connect_failed,{tls_alert,"no application protocol"}}).
+
+%--------------------------------------------------------------------------------
+
+empty_server(Config) when is_list(Config) ->
+ run_failing_handshake(Config,
+ [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
+ [{alpn_preferred_protocols, []}],
+ {connect_failed,{tls_alert,"no application protocol"}}).
+
+%--------------------------------------------------------------------------------
+
+empty_client_empty_server(Config) when is_list(Config) ->
+ run_failing_handshake(Config,
+ [{alpn_advertised_protocols, []}],
+ [{alpn_preferred_protocols, []}],
+ {connect_failed,{tls_alert,"no application protocol"}}).
+
+%--------------------------------------------------------------------------------
+
+no_matching_protocol(Config) when is_list(Config) ->
+ run_failing_handshake(Config,
+ [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
+ [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}],
+ {connect_failed,{tls_alert,"no application protocol"}}).
+
+%--------------------------------------------------------------------------------
+
+client_alpn_and_server_alpn(Config) when is_list(Config) ->
+ run_handshake(Config,
+ [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
+ [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}],
+ {ok, <<"http/1.1">>}).
+
+%--------------------------------------------------------------------------------
+
+client_alpn_and_server_no_support(Config) when is_list(Config) ->
+ run_handshake(Config,
+ [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
+ [],
+ {error, protocol_not_negotiated}).
+
+%--------------------------------------------------------------------------------
+
+client_no_support_and_server_alpn(Config) when is_list(Config) ->
+ run_handshake(Config,
+ [],
+ [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}],
+ {error, protocol_not_negotiated}).
+
+%--------------------------------------------------------------------------------
+
+client_alpn_npn_and_server_alpn(Config) when is_list(Config) ->
+ run_handshake(Config,
+ [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]},
+ {client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"spdy/3">>}}],
+ [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}],
+ {ok, <<"http/1.1">>}).
+
+%--------------------------------------------------------------------------------
+
+client_alpn_npn_and_server_alpn_npn(Config) when is_list(Config) ->
+ run_handshake(Config,
+ [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]},
+ {client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"spdy/3">>}}],
+ [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]},
+ {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.0">>]}],
+ {ok, <<"http/1.1">>}).
+
+%--------------------------------------------------------------------------------
+
+client_alpn_and_server_alpn_npn(Config) when is_list(Config) ->
+ run_handshake(Config,
+ [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
+ [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]},
+ {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.0">>]}],
+ {ok, <<"http/1.1">>}).
+
+%--------------------------------------------------------------------------------
+
+client_renegotiate(Config) when is_list(Config) ->
+ Data = "hello world",
+
+ ClientOpts0 = ?config(client_opts, Config),
+ ClientOpts = [{alpn_advertised_protocols, [<<"http/1.0">>]}] ++ ClientOpts0,
+ ServerOpts0 = ?config(server_opts, Config),
+ ServerOpts = [{alpn_preferred_protocols, [<<"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_alpn, [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_alpn_and_renegotiate_and_send_data, [ExpectedProtocol, Data]}},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok).
+
+%--------------------------------------------------------------------------------
+
+session_reused(Config) when is_list(Config)->
+ ClientOpts0 = ?config(client_opts, Config),
+ ClientOpts = [{alpn_advertised_protocols, [<<"http/1.0">>]}] ++ ClientOpts0,
+ ServerOpts0 = ?config(server_opts, Config),
+ ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0,
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, session_info_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_msg, []}},
+ {options, ClientOpts}]),
+
+ SessionInfo =
+ receive
+ {Server, Info} ->
+ Info
+ end,
+
+ Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
+
+ %% Make sure session is registered
+ ct:sleep(?SLEEP),
+
+ Client1 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_info_result, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ receive
+ {Client1, SessionInfo} ->
+ ok;
+ {Client1, Other} ->
+ ct:fail(Other)
+ end,
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ ssl_test_lib:close(Client1).
+
+%--------------------------------------------------------------------------------
+
+alpn_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,
+ {options,
+ {not_supported_in_sslv3, PrefProtocols}}}).
+
+%--------------------------------------------------------------------------------
+
+alpn_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, {options, {not_supported_in_sslv3, AdvProtocols}}} = ssl:listen(0, ServerOpts).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+
+run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedResult) ->
+ ClientOpts = ClientExtraOpts ++ ?config(client_opts, Config),
+ ServerOpts = ServerExtraOpts ++ ?config(server_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, placeholder, []}},
+ {options, ServerOpts}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ ExpectedResult
+ = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, placeholder, []}},
+ {options, ClientOpts}]).
+
+run_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_alpn, [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_alpn, [ExpectedProtocol, Data]}},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok).
+
+assert_alpn(Socket, Protocol) ->
+ ct:log("Negotiated Protocol ~p, Expecting: ~p ~n",
+ [ssl:negotiated_protocol(Socket), Protocol]),
+ Protocol = ssl:negotiated_protocol(Socket).
+
+assert_alpn_and_renegotiate_and_send_data(Socket, Protocol, Data) ->
+ assert_alpn(Socket, Protocol),
+ ct:log("Renegotiating ~n", []),
+ ok = ssl:renegotiate(Socket),
+ ssl:send(Socket, Data),
+ assert_alpn(Socket, Protocol),
+ ok.
+
+ssl_send_and_assert_alpn(Socket, Protocol, Data) ->
+ assert_alpn(Socket, Protocol),
+ ssl_send(Socket, Data).
+
+ssl_receive_and_assert_alpn(Socket, Protocol, Data) ->
+ assert_alpn(Socket, Protocol),
+ ssl_receive(Socket, Data).
+
+ssl_send(Socket, Data) ->
+ ct:log("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) ->
+ ct:log("Connection info: ~p~n",
+ [ssl:connection_info(Socket)]),
+ receive
+ {ssl, Socket, MoreData} ->
+ ct:log("Received ~p~n",[MoreData]),
+ NewBuffer = Buffer ++ MoreData,
+ case NewBuffer of
+ Data ->
+ ssl:send(Socket, "Got it"),
+ ok;
+ _ ->
+ ssl_receive(Socket, Data, NewBuffer)
+ end;
+ Other ->
+ ct:fail({unexpected_message, Other})
+ after 4000 ->
+ ct:fail({did_not_get, Data})
+ end.
+
+connection_info_result(Socket) ->
+ ssl:connection_info(Socket).