Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dgud/ssl/options work #6236

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 151 additions & 55 deletions lib/ssl/src/ssl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@
connection_information/1,
connection_information/2]).
%% Misc
-export([handle_options/2,
handle_options/3,
-export([handle_options/3,
update_options/3,
option_rules/0,
tls_version/1,
suite_to_str/1,
suite_to_openssl_str/1,
Expand Down Expand Up @@ -640,7 +641,7 @@ listen(_Port, []) ->
{error, nooptions};
listen(Port, Options0) ->
try
{ok, Config} = handle_options(Options0, server),
{ok, Config} = handle_options(Options0, server, undefined),
do_listen(Port, Config, Config#config.connection_cb)
catch
Error = {error, _} ->
Expand Down Expand Up @@ -1513,36 +1514,31 @@ do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_g
do_listen(Port, Config, dtls_gen_connection) ->
dtls_socket:listen(Port, Config).

-spec handle_options([any()], client | server) -> {ok, #config{}};
([any()], ssl_options()) -> ssl_options().

handle_options(Opts, Role) ->
handle_options(undefined, undefined, Opts, Role, undefined).
%% Handle ssl options at handshake, handshake_continue
-spec update_options([any()], client | server, map()) -> map().
update_options(Opts, Role, InheritedSslOpts) when is_map(InheritedSslOpts) ->
{SslOpts, _} = split_options(Opts, option_rules()),
process_options(SslOpts, InheritedSslOpts, #{role => Role, rules => option_rules()}).

handle_options(Opts, Role, InheritedSslOpts) ->
handle_options(undefined, undefined, Opts, Role, InheritedSslOpts).
-spec handle_options([any()], client | server, undefined|host()) -> {ok, #config{}}.
handle_options(Opts, Role, Host) ->
handle_options(undefined, undefined, Opts, Role, Host).

%% Handle ssl options at handshake, handshake_continue
handle_options(_, _, Opts0, Role, InheritedSslOpts) when is_map(InheritedSslOpts) ->
{SslOpts, _} = expand_options(Opts0, ?RULES),
process_options(SslOpts, InheritedSslOpts, #{role => Role,
rules => ?RULES});
%% Handle all options in listen, connect and handshake
handle_options(Transport, Socket, Opts0, Role, Host) ->
{SslOpts0, SockOpts0} = expand_options(Opts0, ?RULES),
{SslOpts0, SockOpts0} = split_options(Opts0, option_rules()),

%% Ensure all options are evaluated at startup
SslOpts1 = add_missing_options(SslOpts0, ?RULES),
SslOpts2 = #{protocol := Protocol}
= process_options(SslOpts1,
#{},
#{role => Role,
host => Host,
rules => ?RULES}),

SslOpts1 = add_missing_options(SslOpts0, option_rules()),

Env = #{role => Role, host => Host, rules => option_rules()},
SslOpts2 = process_options(SslOpts1, #{}, Env),

maybe_client_warn_no_verify(SslOpts2, Role),
SslOpts = maps:without([warn_verify_none], SslOpts2),

%% Handle special options
#{protocol := Protocol} = SslOpts2,
{Sock, Emulated} = emulated_options(Transport, Socket, Protocol, SockOpts0),
ConnetionCb = connection_cb(Protocol),
CbInfo = handle_option_cb_info(Opts0, Protocol),
Expand All @@ -1557,6 +1553,110 @@ handle_options(Transport, Socket, Opts0, Role, Host) ->
}}.


%% This map stores all supported options with default values and
%% list of dependencies:
%% #{<option> => {<default_value>, [<option>]},
%% ...}
option_rules() ->
#{
alpn_advertised_protocols => {undefined, [versions]},
alpn_preferred_protocols => {undefined, [versions]},
anti_replay => {undefined, [versions, session_tickets]},
beast_mitigation => {one_n_minus_one, [versions]},
cacertfile => {undefined, [versions,
verify_fun,
cacerts]},
cacerts => {undefined, [versions]},
cert => {undefined, [versions]},
certs_keys => {undefined, [versions]},
certfile => {<<>>, [versions]},
certificate_authorities => {false, [versions]},
ciphers => {[], [versions]},
client_renegotiation => {undefined, [versions]},
cookie => {true, [versions]},
crl_cache => {{ssl_crl_cache, {internal, []}}, [versions]},
crl_check => {false, [versions]},
customize_hostname_check => {[], [versions]},
depth => {10, [versions]},
dh => {undefined, [versions]},
dhfile => {undefined, [versions]},
early_data => {undefined, [versions,
session_tickets,
use_ticket]},
eccs => {undefined, [versions]},
erl_dist => {false, [versions]},
fail_if_no_peer_cert => {false, [versions]},
fallback => {false, [versions]},
handshake => {full, [versions]},
hibernate_after => {infinity, [versions]},
honor_cipher_order => {false, [versions]},
honor_ecc_order => {undefined, [versions]},
keep_secrets => {false, [versions]},
key => {undefined, [versions]},
keyfile => {undefined, [versions,
certfile]},
key_update_at => {?KEY_USAGE_LIMIT_AES_GCM, [versions]},
log_level => {notice, [versions]},
max_handshake_size => {?DEFAULT_MAX_HANDSHAKE_SIZE, [versions]},
middlebox_comp_mode => {true, [versions]},
max_fragment_length => {undefined, [versions]},
next_protocol_selector => {undefined, [versions]},
next_protocols_advertised => {undefined, [versions]},
%% If enable OCSP stapling
ocsp_stapling => {false, [versions]},
%% Optional arg, if give suggestion of OCSP responders
ocsp_responder_certs => {[], [versions,
ocsp_stapling]},
%% Optional arg, if add nonce extension in request
ocsp_nonce => {true, [versions,
ocsp_stapling]},
padding_check => {true, [versions]},
partial_chain => {fun(_) -> unknown_ca end, [versions]},
password => {"", [versions]},
protocol => {tls, []},
psk_identity => {undefined, [versions]},
receiver_spawn_opts => {[], [versions]},
renegotiate_at => {?DEFAULT_RENEGOTIATE_AT, [versions]},
reuse_session => {undefined, [versions]},
reuse_sessions => {true, [versions]},
secure_renegotiate => {true, [versions]},
sender_spawn_opts => {[], [versions]},
server_name_indication => {undefined, [versions]},
session_tickets => {disabled, [versions]},
signature_algs => {undefined, [versions]},
signature_algs_cert => {undefined, [versions]},
sni_fun => {undefined, [versions,
sni_hosts]},
sni_hosts => {[], [versions]},
srp_identity => {undefined, [versions]},
supported_groups => {undefined, [versions]},
use_ticket => {undefined, [versions]},
user_lookup_fun => {undefined, [versions]},
verify => {verify_none, [versions,
fail_if_no_peer_cert,
partial_chain]},
verify_fun =>
{
{fun(_, {bad_cert, _}, UserState) ->
{valid, UserState};
(_, {extension, #'Extension'{critical = true}}, UserState) ->
%% This extension is marked as critical, so
%% certificate verification should fail if we don't
%% understand the extension. However, this is
%% `verify_none', so let's accept it anyway.
{valid, UserState};
(_, {extension, _}, UserState) ->
{unknown, UserState};
(_, valid, UserState) ->
{valid, UserState};
(_, valid_peer, UserState) ->
{valid, UserState}
end, []},
[versions, verify]},
versions => {[], [protocol]}
}.


%% process_options(SSLOptions, OptionsMap, Env) where
%% SSLOptions is the following tuple:
%% {InOptions, SkippedOptions, Counter}
Expand All @@ -1570,23 +1670,29 @@ handle_options(Transport, Socket, Opts0, Role, Host) ->
%% after each successful pass.
%% If the value of the counter is unchanged at the end of a pass,
%% the processing stops due to faulty input data.
process_options({[], [], _}, OptionsMap, _Env) ->
OptionsMap;
process_options({[], [_|_] = Skipped, Counter}, OptionsMap, Env)
when length(Skipped) < Counter ->
%% Continue handling options if current pass was successful
process_options({Skipped, [], length(Skipped)}, OptionsMap, Env);
process_options({[], [_|_], _Counter}, _OptionsMap, _Env) ->
throw({error, faulty_configuration});
process_options({[{K0,V} = E|T], S, Counter}, OptionsMap0, Env) ->

process_options(Opts, Map, Env) ->
process_options(Opts, [], length(Opts), Map, Env).

process_options([{K0,V} = E|T], S, Counter, OptionsMap0, Env) ->
K = maybe_map_key_internal(K0),
case check_dependencies(K, OptionsMap0, Env) of
true ->
OptionsMap = handle_option(K, V, OptionsMap0, Env),
process_options({T, S, Counter}, OptionsMap, Env);
process_options(T, S, Counter, OptionsMap, Env);
false ->
%% Skip option for next pass
process_options({T, [E|S], Counter}, OptionsMap0, Env)
process_options(T, [E|S], Counter, OptionsMap0, Env)
end;
process_options([], [], _, OptionsMap, _Env) ->
OptionsMap;
process_options([], Skipped, Counter, OptionsMap, Env) ->
case length(Skipped) < Counter of
true ->
%% Continue handling options if current pass was successful
process_options(Skipped, [], length(Skipped), OptionsMap, Env);
false ->
throw({error, faulty_configuration})
end.

handle_option(anti_replay = Option, unbound, OptionsMap, #{rules := Rules}) ->
Expand Down Expand Up @@ -1975,28 +2081,19 @@ dependecies_already_defined(L, OptionsMap) ->
lists:all(Fun, L).


expand_options(Opts0, Rules) ->
split_options(Opts0, Rules) ->
Opts1 = proplists:expand([{binary, [{mode, binary}]},
{list, [{mode, list}]}], Opts0),
{list, [{mode, list}]}], Opts0),
Opts2 = handle_option_format(Opts1, []),

%% Remove deprecated ssl_imp option
Opts = proplists:delete(ssl_imp, Opts2),
AllOpts = maps:keys(Rules),
SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) end,
Opts,
AllOpts ++
[ssl_imp, %% TODO: remove ssl_imp
cb_info,
client_preferred_next_protocols, %% next_protocol_selector
log_alert]), %% obsoleted by log_level

SslOpts0 = Opts -- SockOpts,
SslOpts = {SslOpts0, [], length(SslOpts0)},
{SslOpts, SockOpts}.

DeleteSSLOpts = fun(Key, PropList) -> proplists:delete(Key, PropList) end,
AllOpts = [cb_info, client_preferred_next_protocols] ++ maps:keys(Rules),
SockOpts = lists:foldl(DeleteSSLOpts, Opts, AllOpts),
{Opts -- SockOpts, SockOpts}.

add_missing_options({L0, S, _C}, Rules) ->
add_missing_options(L0, Rules) ->
Fun = fun(K0, Acc) ->
K = maybe_map_key_external(K0),
case proplists:is_defined(K, Acc) of
Expand All @@ -2008,8 +2105,7 @@ add_missing_options({L0, S, _C}, Rules) ->
end
end,
AllOpts = maps:keys(Rules),
L = lists:foldl(Fun, L0, AllOpts),
{L, S, length(L)}.
lists:foldl(Fun, L0, AllOpts).

default_value(Key, Rules) ->
{Default, _} = maps:get(Key, Rules, {undefined, []}),
Expand Down Expand Up @@ -2450,7 +2546,7 @@ validate_option(verify_fun, {Fun, _} = Value, _) when is_function(Fun) ->
validate_option(versions, Versions, _) ->
validate_versions(Versions, Versions);
validate_option(Opt, undefined = Value, _) ->
AllOpts = maps:keys(?RULES),
AllOpts = maps:keys(option_rules()),
case lists:member(Opt, AllOpts) of
true ->
Value;
Expand Down
4 changes: 2 additions & 2 deletions lib/ssl/src/ssl_gen_statem.erl
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ initial_hello({call, From}, {start, {Opts, EmOpts}, Timeout},
ssl_options = OrigSSLOptions,
socket_options = SockOpts} = State0) ->
try
SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions),
SslOpts = ssl:update_options(Opts, Role, OrigSSLOptions),
State = ssl_config(SslOpts, Role, State0),
initial_hello({call, From}, {start, Timeout},
State#state{ssl_options = SslOpts,
Expand Down Expand Up @@ -1325,7 +1325,7 @@ update_ssl_options_from_sni(#{sni_fun := SNIFun,
_ ->
VersionsOpt = proplists:get_value(versions, SSLOptions, []),
FallBackOptions = filter_for_versions(VersionsOpt, OrigSSLOptions),
ssl:handle_options(SSLOptions, server, FallBackOptions)
ssl:update_options(SSLOptions, server, FallBackOptions)
end.

filter_for_versions([], OrigSSLOptions) ->
Expand Down
Loading