Skip to content

Commit 503827c

Browse files
committed
Merge branch 'maint'
2 parents dc676d9 + c07e207 commit 503827c

File tree

3 files changed

+110
-48
lines changed

3 files changed

+110
-48
lines changed

lib/ssl/src/ssl.erl

+9-4
Original file line numberDiff line numberDiff line change
@@ -1741,8 +1741,12 @@ Options specific to the server side, or with different semantics for the client
17411741
If the server receives a SNI (Server Name Indication) from the
17421742
client, the given fun `SNIFun` will be called to retrieve
17431743
[`server_option()`](`t:server_option/0`) for the indicated
1744-
hosts. These options will override previously specified options for
1745-
that host.
1744+
server. These options will override previously specified server options.
1745+
The sni_fun can indicate that it does not recognize the server name by
1746+
returning `unrecognized` in which case the connection will be closed with an
1747+
`unrecognized_name` alert. If the
1748+
sni_fun returns `undefined` the connection will be attempted with the default
1749+
options supplied to `listen/2` or [`handshake/2,3`](`handshake/2`).
17461750
17471751
> #### Note {: .info }
17481752
The options `sni_fun` and `sni_hosts` are mutually exclusive.
@@ -1751,7 +1755,8 @@ Options specific to the server side, or with different semantics for the client
17511755
17521756
If the server receives a SNI (Server Name Indication) from the client matching a
17531757
host listed in the `sni_hosts` option, the specific options for that host will
1754-
override previously specified options.
1758+
override previously specified options. If no match is found it behaves as
1759+
option sni_fun that returns `undefined`.
17551760
17561761
> #### Note {: .info }
17571762
The options `sni_fun` and `sni_hosts` are mutually exclusive.
@@ -1762,7 +1767,7 @@ Options specific to the server side, or with different semantics for the client
17621767
common_option_cert() |
17631768
{alpn_preferred_protocols, AppProtocols::[binary()]}|
17641769
{sni_hosts, SNIHosts::[{inet:hostname(), [server_option() | common_option()]}]} |
1765-
{sni_fun, SNIFun:: fun((string()) -> [])} |
1770+
{sni_fun, SNIFun:: fun((string()) -> [server_option() | common_option()] | 'unrecognized' | 'undefined')} |
17661771
server_option_pre_tls13() |
17671772
common_option_pre_tls13() |
17681773
server_option_tls13() |

lib/ssl/src/ssl_gen_statem.erl

+44-14
Original file line numberDiff line numberDiff line change
@@ -480,9 +480,13 @@ handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{
480480
ssl_options = #{protocol := Protocol}} = State0) ->
481481
case check_hostname(Hostname) of
482482
ok ->
483-
484-
proc_lib:set_label({Protocol, ?SERVER_ROLE, erlang:iolist_to_binary(Hostname), Port}),
485-
{ok, handle_sni_hostname(Hostname, State0)};
483+
case handle_sni_hostname(Hostname, State0) of
484+
#alert{} = OptAlert ->
485+
{error, OptAlert};
486+
State ->
487+
proc_lib:set_label({Protocol, ?SERVER_ROLE, erlang:iolist_to_binary(Hostname), Port}),
488+
{ok, State}
489+
end;
486490
#alert{} = Alert ->
487491
{error, Alert}
488492
end.
@@ -1276,15 +1280,18 @@ handle_sni_hostname(Hostname,
12761280
handshake_env = HsEnv,
12771281
connection_env = CEnv,
12781282
ssl_options = Opts} = State0) ->
1283+
%% RFC6060: "If the server understood the ClientHello extension but
1284+
%% does not recognize the server name, the server SHOULD take one of two
1285+
%% actions: either abort the handshake by sending a fatal-level
1286+
%% unrecognized_name(112) alert or continue the handshake."
1287+
%% Realized as use_default_options | #alert{description = ?UNRECOGNIZED_NAME}
12791288
case update_ssl_options_from_sni(Opts, Hostname) of
1280-
undefined ->
1281-
%% RFC6060: "If the server understood the ClientHello extension but
1282-
%% does not recognize the server name, the server SHOULD take one of two
1283-
%% actions: either abort the handshake by sending a fatal-level
1284-
%% unrecognized_name(112) alert or continue the handshake."
1289+
use_original_options ->
12851290
State0#state{handshake_env = HsEnv#handshake_env{sni_hostname = Hostname}};
1291+
#alert{} = Alert ->
1292+
Alert;
12861293
NewOptions ->
1287-
{ok, #{cert_db_ref := Ref,
1294+
{ok, #{cert_db_ref := Ref,
12881295
cert_db_handle := CertDbHandle,
12891296
fileref_db_handle := FileRefHandle,
12901297
session_cache := CacheHandle,
@@ -1309,13 +1316,36 @@ handle_sni_hostname(Hostname,
13091316
end.
13101317

13111318
update_ssl_options_from_sni(#{sni_fun := SNIFun} = OrigSSLOptions, SNIHostname) ->
1312-
case SNIFun(SNIHostname) of
1319+
try SNIFun(SNIHostname) of
13131320
undefined ->
1314-
undefined;
1321+
use_original_options;
1322+
unrecognized ->
1323+
?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME, {sni, SNIHostname});
13151324
SSLOptions ->
1316-
VersionsOpt = proplists:get_value(versions, SSLOptions, []),
1317-
FallBackOptions = filter_for_versions(VersionsOpt, OrigSSLOptions),
1318-
ssl_config:update_options(SSLOptions, server, FallBackOptions)
1325+
try
1326+
FallBackOptions = fallback_options(SSLOptions, OrigSSLOptions),
1327+
ssl_config:update_options(SSLOptions, server, FallBackOptions)
1328+
catch
1329+
throw:#alert{} = Alert ->
1330+
Alert;
1331+
throw:Reason ->
1332+
?LOG_ERROR("sni_fun provided erroneous options : ~p~n", [Reason]),
1333+
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, sni_fun_provided_erroneous_options)
1334+
end
1335+
catch
1336+
Error:Reason:ST ->
1337+
?LOG_ERROR("sni_fun crashed ~p ~p~n ~p~n", [Error, Reason, {stacktrace, ST}]),
1338+
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, sni_fun_crashed)
1339+
end.
1340+
1341+
fallback_options(SSLOptions, OrigSSLOptions) ->
1342+
VersionsOpt = proplists:get_value(versions, SSLOptions, []),
1343+
try filter_for_versions(VersionsOpt, OrigSSLOptions) of
1344+
FallBackOptions ->
1345+
FallBackOptions
1346+
catch _:_:ST ->
1347+
?LOG_ERROR("sni_fun provided erroneous options : ~p ~n ~p ~n", [VersionsOpt, {stacktrace, ST}]),
1348+
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, sni_fun_provided_erroneous_versions))
13191349
end.
13201350

13211351
filter_for_versions([], OrigSSLOptions) ->

lib/ssl/test/ssl_sni_SUITE.erl

+57-30
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@
3939
end_per_testcase/2]).
4040

4141
%% Testcases
42-
-export([no_sni_header/1,
42+
-export([no_sni_ext/1,
4343
sni_match/1,
4444
sni_no_match/1,
45-
no_sni_header_fun/1,
45+
no_sni_ext_fun/1,
4646
sni_match_fun/1,
4747
sni_no_match_fun/1,
48+
sni_fail_fun/1,
49+
sni_crash_fun/1,
4850
dns_name/1,
4951
ip_fallback/1,
5052
no_ip_fallback/1,
@@ -88,12 +90,14 @@ groups() ->
8890
].
8991

9092
sni_tests() ->
91-
[no_sni_header,
93+
[no_sni_ext,
9294
sni_match,
9395
sni_no_match,
94-
no_sni_header_fun,
96+
no_sni_ext_fun,
9597
sni_match_fun,
9698
sni_no_match_fun,
99+
sni_fail_fun,
100+
sni_crash_fun,
97101
dns_name,
98102
ip_fallback,
99103
no_ip_fallback,
@@ -148,14 +152,14 @@ end_per_testcase(_TestCase, Config) ->
148152
%%--------------------------------------------------------------------
149153
%% Test Cases --------------------------------------------------------
150154
%%--------------------------------------------------------------------
151-
no_sni_header(Config) ->
155+
no_sni_ext(Config) ->
152156
{ClientNode, ServerNode, HostName} = ssl_test_lib:run_where(Config),
153157
ServerOptions = ssl_test_lib:ssl_options(proplists:get_value(sni_server_opts, Config), Config),
154158
ClientOptions = ssl_test_lib:ssl_options([{server_name_indication, disable} |
155159
proplists:get_value(client_local_opts, Config)], Config),
156160
basic_sni_test(ServerNode, ServerOptions, ClientNode, ClientOptions, HostName, undefined).
157161

158-
no_sni_header_fun(Config) ->
162+
no_sni_ext_fun(Config) ->
159163
{ClientNode, ServerNode, HostName} = ssl_test_lib:run_where(Config),
160164
[{sni_hosts, ServerSNIConf}| DefaultConf] = proplists:get_value(sni_server_opts, Config),
161165
SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, []) end,
@@ -174,7 +178,7 @@ sni_match(Config) ->
174178
sni_match_fun(Config) ->
175179
{ClientNode, ServerNode, HostName} = ssl_test_lib:run_where(Config),
176180
[{sni_hosts, ServerSNIConf}| DefaultConf] = proplists:get_value(sni_server_opts, Config),
177-
SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end,
181+
SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, unrecognized) end,
178182
ServerOptions = ssl_test_lib:ssl_options(DefaultConf, Config) ++ [{sni_fun, SNIFun}],
179183
ClientOptions = ssl_test_lib:ssl_options([{server_name_indication, HostName} |
180184
proplists:get_value(client_opts, Config)], Config),
@@ -186,15 +190,38 @@ sni_no_match(Config) ->
186190
ClientOptions = ssl_test_lib:ssl_options([{server_name_indication, HostName} |
187191
proplists:get_value(client_opts, Config)], Config),
188192
ServerOptions = ssl_test_lib:ssl_options(DefaultConf, Config),
189-
basic_sni_alert_test(ServerNode, ServerOptions, ClientNode, ClientOptions, HostName).
193+
basic_sni_alert_test(ServerNode, ServerOptions, ClientNode, ClientOptions, HostName, handshake_failure).
190194

191195
sni_no_match_fun(Config) ->
192196
{ClientNode, ServerNode, HostName} = ssl_test_lib:run_where(Config),
193-
[{sni_hosts, _}| DefaultConf] = proplists:get_value(sni_server_opts, Config),
194-
ServerOptions = ssl_test_lib:ssl_options(DefaultConf, Config),
195-
ClientOptions = ssl_test_lib:ssl_options([{server_name_indication, HostName} |
197+
[{sni_hosts, ServerSNIConf}| DefaultConf] = proplists:get_value(sni_server_opts, Config),
198+
SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, unrecognized) end,
199+
ServerOptions = ssl_test_lib:ssl_options(DefaultConf, Config) ++ [{sni_fun, SNIFun}],
200+
ClientOptions = ssl_test_lib:ssl_options([{server_name_indication, "localhost"} |
196201
proplists:get_value(client_local_opts, Config)], Config),
197-
basic_sni_alert_test(ServerNode, ServerOptions, ClientNode, ClientOptions, HostName).
202+
basic_sni_alert_test(ServerNode, ServerOptions, ClientNode, ClientOptions, HostName, unrecognized_name).
203+
204+
sni_fail_fun(Config) ->
205+
{ClientNode, ServerNode, HostName} = ssl_test_lib:run_where(Config),
206+
[_| DefaultConf] = proplists:get_value(sni_server_opts, Config),
207+
ServerOptions = ssl_test_lib:ssl_options(DefaultConf, Config) ,
208+
ClientOptions = ssl_test_lib:ssl_options([{server_name_indication, HostName} |
209+
proplists:get_value(client_opts, Config)], Config),
210+
basic_sni_alert_test(ServerNode, ServerOptions ++ [{sni_fun, fun(_Domain) -> [{versions, ['tlsv1.5']}] end}],
211+
ClientNode, ClientOptions, HostName, handshake_failure),
212+
basic_sni_alert_test(ServerNode, ServerOptions ++ [{sni_fun, fun(_Domain) -> [{verify, foobar}] end}],
213+
ClientNode, ClientOptions, HostName, handshake_failure).
214+
215+
sni_crash_fun(Config) ->
216+
{ClientNode, ServerNode, HostName} = ssl_test_lib:run_where(Config),
217+
[_| DefaultConf] = proplists:get_value(sni_server_opts, Config),
218+
SNIFun = fun(Domain) -> Domain = nomatch end,
219+
ServerOptions = ssl_test_lib:ssl_options(DefaultConf, Config) ++ [{sni_fun, SNIFun}],
220+
ClientOptions = ssl_test_lib:ssl_options([{server_name_indication, HostName} |
221+
proplists:get_value(client_opts, Config)], Config),
222+
basic_sni_alert_test(ServerNode, ServerOptions, ClientNode, ClientOptions, HostName, handshake_failure).
223+
224+
198225

199226
dns_name(Config) ->
200227
Hostname = "OTP.test.server",
@@ -215,12 +242,12 @@ dns_name(Config) ->
215242
Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
216243
ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
217244
ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
218-
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config),
219-
successfull_connect(ServerConf, [{verify, verify_peer},
245+
unsuccessful_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config, handshake_failure),
246+
successful_connect(ServerConf, [{verify, verify_peer},
220247
{server_name_indication, Hostname} | ClientConf], undefined, Config),
221-
unsuccessfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, "foo"} | ClientConf],
222-
undefined, Config),
223-
successfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, disable} | ClientConf],
248+
unsuccessful_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, "foo"} | ClientConf],
249+
undefined, Config, handshake_failure),
250+
successful_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, disable} | ClientConf],
224251
undefined, Config).
225252

226253
ip_fallback(Config) ->
@@ -246,10 +273,10 @@ ip_fallback(Config) ->
246273
Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
247274
ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
248275
ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
249-
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
250-
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config),
251-
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IPStr, Config),
252-
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], list_to_atom(Hostname), Config).
276+
successful_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
277+
successful_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config),
278+
successful_connect(ServerConf, [{verify, verify_peer} | ClientConf], IPStr, Config),
279+
successful_connect(ServerConf, [{verify, verify_peer} | ClientConf], list_to_atom(Hostname), Config).
253280

254281
no_ip_fallback(Config) ->
255282
Hostname = net_adm:localhost(),
@@ -274,9 +301,9 @@ no_ip_fallback(Config) ->
274301
Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
275302
ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
276303
ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
277-
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
278-
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config),
279-
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IPStr, Config).
304+
successful_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
305+
unsuccessful_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config, handshake_failure),
306+
unsuccessful_connect(ServerConf, [{verify, verify_peer} | ClientConf], IPStr, Config, handshake_failure).
280307

281308
dns_name_reuse(Config) ->
282309
SNIHostname = "OTP.test.server",
@@ -302,7 +329,7 @@ dns_name_reuse(Config) ->
302329

303330
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
304331

305-
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config),
332+
unsuccessful_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config, handshake_failure),
306333

307334
Server =
308335
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -475,19 +502,19 @@ basic_sni_test(ServerNode, ServerOptions, ClientNode, ClientOptions, HostName, E
475502
ssl_test_lib:close(Server),
476503
ssl_test_lib:close(Client).
477504

478-
basic_sni_alert_test(ServerNode, ServerOptions, ClientNode, ClientOptions, HostName) ->
505+
basic_sni_alert_test(ServerNode, ServerOptions, ClientNode, ClientOptions, HostName, Alert) ->
479506
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
480507
{from, self()}, {mfa, {ssl_test_lib, no_result, []}},
481508
{options, ServerOptions}]),
482509
Port = ssl_test_lib:inet_port(Server),
483510
Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
484511
{host, HostName}, {from, self()},
485512
{options, [{verify, verify_peer} | ClientOptions]}]),
486-
ssl_test_lib:check_client_alert(Client, handshake_failure),
513+
ssl_test_lib:check_client_alert(Client, Alert),
487514
ssl_test_lib:close(Server),
488515
ssl_test_lib:close(Client).
489516

490-
successfull_connect(ServerOptions, ClientOptions, Hostname0, Config) ->
517+
successful_connect(ServerOptions, ClientOptions, Hostname0, Config) ->
491518
{ClientNode, ServerNode, Hostname1} = ssl_test_lib:run_where(Config),
492519
Hostname = host_name(Hostname0, Hostname1),
493520
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -502,7 +529,7 @@ successfull_connect(ServerOptions, ClientOptions, Hostname0, Config) ->
502529
ssl_test_lib:close(Server),
503530
ssl_test_lib:close(Client).
504531

505-
unsuccessfull_connect(ServerOptions, ClientOptions, Hostname0, Config) ->
532+
unsuccessful_connect(ServerOptions, ClientOptions, Hostname0, Config, Alert) ->
506533
{ClientNode, ServerNode, Hostname1} = ssl_test_lib:run_where(Config),
507534
Hostname = host_name(Hostname0, Hostname1),
508535
Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
@@ -514,7 +541,7 @@ unsuccessfull_connect(ServerOptions, ClientOptions, Hostname0, Config) ->
514541
{from, self()},
515542
{options, ClientOptions}]),
516543

517-
ssl_test_lib:check_server_alert(Server, Client, handshake_failure).
544+
ssl_test_lib:check_server_alert(Server, Client, Alert).
518545

519546
host_name(undefined, Hostname) ->
520547
Hostname;

0 commit comments

Comments
 (0)