diff options
Diffstat (limited to 'lib/ssl/src/ssl.erl')
-rw-r--r-- | lib/ssl/src/ssl.erl | 829 |
1 files changed, 407 insertions, 422 deletions
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 0d6501b5ee..aae79d7625 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -92,13 +92,12 @@ connection_information/1, connection_information/2]). %% Misc --export([handle_options/2, +-export([handle_options/2, + handle_options/3, tls_version/1, - new_ssl_options/3, suite_to_str/1, suite_to_openssl_str/1, - str_to_suite/1, - options_to_map/1]). + str_to_suite/1]). -deprecated({ssl_accept, 1, eventually}). -deprecated({ssl_accept, 2, eventually}). @@ -439,7 +438,6 @@ elliptic_curves => [public_key:oid()], sni => hostname()}. % exported %% ------------------------------------------------------------------------------------------------------- --define(SSL_OPTIONS, record_info(fields, ssl_options)). %%%-------------------------------------------------------------------- %%% API @@ -501,7 +499,8 @@ connect(Socket, SslOptions) when is_port(Socket) -> connect(Socket, SslOptions0, Timeout) when is_port(Socket), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> - CbInfo = handle_option(cb_info, SslOptions0, default_cb_info(tls)), + CbInfo = handle_option_cb_info(SslOptions0, tls), + Transport = element(1, CbInfo), EmulatedOptions = tls_socket:emulated_options(), {ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions), @@ -687,9 +686,10 @@ handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> handshake(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> handshake(Socket, Timeout); -handshake(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts, Timeout) when +handshake(#sslsocket{fd = {_, _, _, Trackers}} = Socket, SslOpts, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> try + Tracker = proplists:get_value(option_tracker, Trackers), {ok, EmOpts, _} = tls_socket:get_all_opts(Tracker), ssl_connection:handshake(Socket, {SslOpts, tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout) @@ -707,7 +707,8 @@ handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout) end; handshake(Socket, SslOptions, Timeout) when is_port(Socket), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> - CbInfo = handle_option(cb_info, SslOptions, default_cb_info(tls)), + CbInfo = handle_option_cb_info(SslOptions, tls), + Transport = element(1, CbInfo), EmulatedOptions = tls_socket:emulated_options(), {ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions), @@ -1461,6 +1462,7 @@ do_listen(Port, Config, dtls_connection) -> dtls_socket:listen(Port, Config). + -spec handle_options([any()], client | server) -> {ok, #config{}}; ([any()], ssl_options()) -> ssl_options(). @@ -1468,255 +1470,360 @@ handle_options(Opts, Role) -> handle_options(Opts, Role, undefined). -%% Handle ssl options at handshake_continue -handle_options(Opts0, #{protocol := Protocol, - cacerts := CaCerts0, - cacertfile := CaCertFile0} = InheritedSslOpts, _) -> - RecordCB = record_cb(Protocol), - CaCerts = handle_option(cacerts, Opts0, CaCerts0), - {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, - VerifyClientOnce} = handle_verify_options(Opts0, CaCerts), - CaCertFile = case proplists:get_value(cacertfile, Opts0, CaCertFile0) of - undefined -> - CaCertDefault; - CAFile -> - CAFile - end, - - NewVerifyOpts = InheritedSslOpts#{cacerts => CaCerts, - cacertfile => CaCertFile, - verify => Verify, - verify_fun => VerifyFun, - partial_chain => PartialChainHanlder, - fail_if_no_peer_cert => FailIfNoPeerCert, - verify_client_once => VerifyClientOnce}, - SslOpts1 = lists:foldl(fun(Key, PropList) -> - proplists:delete(Key, PropList) - end, Opts0, [cacerts, cacertfile, verify, verify_fun, partial_chain, - fail_if_no_peer_cert, verify_client_once]), - case handle_option(versions, SslOpts1, []) of - [] -> - new_ssl_options(SslOpts1, NewVerifyOpts, RecordCB); - Value -> - Versions0 = [RecordCB:protocol_version(Vsn) || Vsn <- Value], - Versions1 = lists:sort(fun RecordCB:is_higher/2, Versions0), - new_ssl_options(proplists:delete(versions, SslOpts1), - NewVerifyOpts#{versions => Versions1}, record_cb(Protocol)) - end; - +%% 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(Opts0, Role, Host) -> - Opts = proplists:expand([{binary, [{mode, binary}]}, - {list, [{mode, list}]}], Opts0), - assert_proplist(Opts), - RecordCb = record_cb(Opts), - CaCerts = handle_option(cacerts, Opts, undefined), - - {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, VerifyClientOnce} = - handle_verify_options(Opts, CaCerts), - - CertFile = handle_option(certfile, Opts, <<>>), - - [HighestVersion|_] = Versions = - case handle_option(versions, Opts, []) of - [] -> - RecordCb:supported_protocol_versions(); - Vsns -> - Versions0 = [RecordCb:protocol_version(Vsn) || Vsn <- Vsns], - lists:sort(fun RecordCb:is_higher/2, Versions0) - end, - - Protocol = handle_option(protocol, Opts, tls), + {SslOpts0, SockOpts} = expand_options(Opts0, ?RULES), + + %% Ensure all options are evaluated at startup + SslOpts1 = add_missing_options(SslOpts0, ?RULES), + SslOpts = #{protocol := Protocol, + versions := Versions} + = process_options(SslOpts1, + #{}, + #{role => Role, + host => Host, + rules => ?RULES}), case Versions of [{3, 0}] -> - reject_alpn_next_prot_options(Opts); + reject_alpn_next_prot_options(SslOpts0); _ -> ok end, - - SSLOptions0 = #ssl_options{ - versions = Versions, - verify = validate_option(verify, Verify), - verify_fun = VerifyFun, - partial_chain = PartialChainHanlder, - fail_if_no_peer_cert = FailIfNoPeerCert, - verify_client_once = VerifyClientOnce, - depth = handle_option(depth, Opts, 1), - cert = handle_option(cert, Opts, undefined), - certfile = CertFile, - key = handle_option(key, Opts, undefined), - keyfile = handle_option(keyfile, Opts, CertFile), - password = handle_option(password, Opts, ""), - cacerts = CaCerts, - cacertfile = handle_option(cacertfile, Opts, CaCertDefault), - dh = handle_option(dh, Opts, undefined), - dhfile = handle_option(dhfile, Opts, undefined), - user_lookup_fun = handle_option(user_lookup_fun, Opts, undefined), - psk_identity = handle_option(psk_identity, Opts, undefined), - srp_identity = handle_option(srp_identity, Opts, undefined), - ciphers = handle_option(ciphers, Opts, undefined, undefined, undefined, HighestVersion), - - eccs = handle_option(eccs, Opts, undefined, undefined, undefined, HighestVersion), - - supported_groups = handle_option(supported_groups, Opts, undefined, undefined, undefined, - HighestVersion), - signature_algs = handle_option(signature_algs, Opts, undefined, Role, undefined, HighestVersion), - signature_algs_cert = handle_option(signature_algs_cert, Opts, undefined, undefined, undefined, - HighestVersion), - reuse_sessions = handle_option(reuse_sessions, Opts, undefined, Role), - - reuse_session = handle_option(reuse_session, Opts, undefined, Role), - - secure_renegotiate = handle_option(secure_renegotiate, Opts, true), - client_renegotiation = handle_option(client_renegotiation, Opts, undefined, Role), - renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT), - hibernate_after = handle_option(hibernate_after, Opts, infinity), - erl_dist = handle_option(erl_dist, Opts, false), - alpn_advertised_protocols = - handle_option(alpn_advertised_protocols, Opts, undefined), - alpn_preferred_protocols = - handle_option(alpn_preferred_protocols, Opts, undefined), - next_protocols_advertised = - handle_option(next_protocols_advertised, Opts, undefined), - next_protocol_selector = - handle_option(next_protocol_selector, Opts, undefined), - server_name_indication = - handle_option(server_name_indication, Opts, undefined, Role, Host), - sni_hosts = handle_option(sni_hosts, Opts, []), - sni_fun = handle_option(sni_fun, Opts, undefined), - honor_cipher_order = handle_option(honor_cipher_order, Opts, undefined, Role), - honor_ecc_order = handle_option(honor_ecc_order, Opts, undefined, Role), - protocol = Protocol, - padding_check = proplists:get_value(padding_check, Opts, true), - beast_mitigation = handle_option(beast_mitigation, Opts, one_n_minus_one), - fallback = handle_option(fallback, Opts, undefined, Role), - - crl_check = handle_option(crl_check, Opts, false), - crl_cache = handle_option(crl_cache, Opts, {ssl_crl_cache, {internal, []}}), - max_handshake_size = handle_option(max_handshake_size, Opts, ?DEFAULT_MAX_HANDSHAKE_SIZE), - handshake = handle_option(handshake, Opts, full), - customize_hostname_check = handle_option(customize_hostname_check, Opts, []) - }, - LogLevel = handle_option(log_alert, Opts, true), - SSLOptions1 = SSLOptions0#ssl_options{ - log_level = handle_option(log_level, Opts, LogLevel) - }, - - CbInfo = handle_option(cb_info, Opts, default_cb_info(Protocol)), - - SockOpts = lists:foldl(fun(Key, PropList) -> - proplists:delete(Key, PropList) - end, Opts, ?SSL_OPTIONS ++ - [ssl_imp, %% TODO: remove ssl_imp - client_preferred_next_protocols, %% next_protocol_selector - log_alert, - cb_info]), + %% Handle special options {Sock, Emulated} = emulated_options(Protocol, SockOpts), - ConnetionCb = connection_cb(Opts), - SSLOptions = options_to_map(SSLOptions1), - {ok, #config{ssl = SSLOptions, emulated = Emulated, inet_ssl = Sock, - inet_user = Sock, transport_info = CbInfo, connection_cb = ConnetionCb - }}. - -%% handle_option(OptionName, Opts, Default, Role, Role) -> -%% handle_option(OptionName, Opts, Default); -%% handle_option(_, _, undefined = Value, _, _) -> -%% Value. - -handle_option(OptionName, Opts, Default) -> - handle_option(OptionName, Opts, Default, undefined, undefined, undefined). + ConnetionCb = connection_cb(Protocol), + CbInfo = handle_option_cb_info(Opts0, Protocol), + + {ok, #config{ + ssl = SslOpts, + emulated = Emulated, + inet_ssl = Sock, + inet_user = Sock, + transport_info = CbInfo, + connection_cb = ConnetionCb + }}. + + +%% process_options(SSLOptions, OptionsMap, Env) where +%% SSLOptions is the following tuple: +%% {InOptions, SkippedOptions, Counter} %% -handle_option(OptionName, Opts, Default, Role) -> - handle_option(OptionName, Opts, Default, Role, undefined, undefined). -%% -handle_option(OptionName, Opts, Default, Role, Host) -> - handle_option(OptionName, Opts, Default, Role, Host, undefined). -%% -handle_option(sni_fun, Opts, Default, _Role, _Host, _Version) -> - OptFun = validate_option(sni_fun, - proplists:get_value(sni_fun, Opts, Default)), - OptHosts = proplists:get_value(sni_hosts, Opts, undefined), - case {OptFun, OptHosts} of - {Default, _} -> - Default; - {_, undefined} -> - OptFun; +%% The list of options is processed in multiple passes. When +%% processing an option all dependencies must already be resolved. +%% If there are unresolved dependencies the option will be +%% skipped and processed in a subsequent pass. +%% Counter is equal to the number of unprocessed options at +%% the beginning of a pass. Its value must monotonically decrease +%% 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) -> + 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); + false -> + %% Skip option for next pass + process_options({T, [E|S], Counter}, OptionsMap0, Env) + end. + + +handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts, + verify := Verify, + verify_fun := VerifyFun} = OptionsMap, _Env) + when Verify =:= verify_none orelse + Verify =:= 0 -> + Value = validate_option(Option, ca_cert_default(verify_none, VerifyFun, CaCerts)), + OptionsMap#{Option => Value}; +handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts, + verify := Verify, + verify_fun := VerifyFun} = OptionsMap, _Env) + when Verify =:= verify_peer orelse + Verify =:= 1 orelse + Verify =:= 2 -> + Value = validate_option(Option, ca_cert_default(verify_peer, VerifyFun, CaCerts)), + OptionsMap#{Option => Value}; +handle_option(cacertfile = Option, Value0, OptionsMap, _Env) -> + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(ciphers = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := Rules}) -> + Value = handle_cipher_option(default_value(Option, Rules), HighestVersion), + OptionsMap#{Option => Value}; +handle_option(ciphers = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) -> + Value = handle_cipher_option(Value0, HighestVersion), + OptionsMap#{Option => Value}; +handle_option(client_renegotiation = Option, unbound, OptionsMap, #{role := Role}) -> + Value = default_option_role(server, true, Role), + OptionsMap#{Option => Value}; +handle_option(client_renegotiation = Option, Value0, OptionsMap, #{role := Role}) -> + assert_role(server_only, Role, Option, Value0), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(eccs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) -> + Value = handle_eccs_option(eccs(), HighestVersion), + OptionsMap#{Option => Value}; +handle_option(eccs = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) -> + Value = handle_eccs_option(Value0, HighestVersion), + OptionsMap#{Option => Value}; +handle_option(fallback = Option, unbound, OptionsMap, #{role := Role}) -> + Value = default_option_role(client, false, Role), + OptionsMap#{Option => Value}; +handle_option(fallback = Option, Value0, OptionsMap, #{role := Role}) -> + assert_role(client_only, Role, Option, Value0), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(honor_cipher_order = Option, unbound, OptionsMap, #{role := Role}) -> + Value = default_option_role(server, false, Role), + OptionsMap#{Option => Value}; +handle_option(honor_cipher_order = Option, Value0, OptionsMap, #{role := Role}) -> + assert_role(server_only, Role, Option, Value0), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(honor_ecc_order = Option, unbound, OptionsMap, #{role := Role}) -> + Value = default_option_role(server, false, Role), + OptionsMap#{Option => Value}; +handle_option(honor_ecc_order = Option, Value0, OptionsMap, #{role := Role}) -> + assert_role(server_only, Role, Option, Value0), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(keyfile = Option, unbound, #{certfile := CertFile} = OptionsMap, _Env) -> + Value = validate_option(Option, CertFile), + OptionsMap#{Option => Value}; +handle_option(next_protocol_selector = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = default_value(Option, Rules), + OptionsMap#{Option => Value}; +handle_option(next_protocol_selector = Option, Value0, OptionsMap, _Env) -> + Value = make_next_protocol_selector( + validate_option(client_preferred_next_protocols, Value0)), + OptionsMap#{Option => Value}; +handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) -> + Value = + case Role of + client -> + undefined; + server -> + fun(_, _, _, _) -> true end + end, + OptionsMap#{Option => Value}; +handle_option(reuse_session = Option, Value0, OptionsMap, _Env) -> + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +%% TODO: validate based on role +handle_option(reuse_sessions = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(reuse_sessions = Option, Value0, OptionsMap, _Env) -> + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(anti_replay = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(anti_replay = Option, Value0, + #{session_tickets := SessionTickets} = OptionsMap, #{rules := Rules}) -> + case SessionTickets of + stateless -> + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; _ -> - throw({error, {conflict_options, [sni_fun, sni_hosts]}}) - end; -handle_option(cb_info, Opts, Default, _Role, _Host, _Version) -> - CbInfo = proplists:get_value(cb_info, Opts, Default), - true = validate_option(cb_info, CbInfo), - handle_cb_info(CbInfo, Default); -handle_option(ciphers = Key, Opts, _Default, _Role, _Host, HighestVersion) -> - handle_cipher_option(proplists:get_value(Key, Opts, []), - HighestVersion); -handle_option(eccs = Key, Opts, _Default, _Role, _Host, HighestVersion) -> - handle_eccs_option(proplists:get_value(Key, Opts, eccs()), - HighestVersion); -handle_option(supported_groups = Key, Opts, _Default, _Role, _Host, HighestVersion) -> - handle_supported_groups_option(proplists:get_value(Key, Opts, groups(default)), - HighestVersion); -handle_option(signature_algs = Key, Opts, _Default, Role, _Host, HighestVersion) -> - handle_hashsigns_option( - proplists:get_value(Key, - Opts, - default_option_role_sign_algs(server, - tls_v1:default_signature_algs(HighestVersion), - Role, - HighestVersion)), - tls_version(HighestVersion)); -handle_option(signature_algs_cert = Key, Opts, _Default, _Role, _Host, HighestVersion) -> - handle_signature_algorithms_option( - proplists:get_value(Key, - Opts, - undefined), %% Do not send by default - tls_version(HighestVersion)); -handle_option(reuse_sessions = Key, Opts, _Default, client, _Host, _Version) -> - Value = proplists:get_value(Key, Opts, true), - validate_option(Key, Value), - Value; -handle_option(reuse_sessions = Key, Opts0, _Default, server, _Host, _Version) -> - Opts = proplists:delete({Key, save}, Opts0), - Value = proplists:get_value(Key, Opts, true), - validate_option(Key, Value), - Value; -handle_option(reuse_session = Key, Opts, _Default, client, _Host, _Version) -> - Value = proplists:get_value(Key, Opts, undefined), - validate_option(Key, Value), - Value; -handle_option(reuse_session = Key, Opts, _Default, server, _Host, _Version) -> - ReuseSessionFun = fun(_, _, _, _) -> true end, - Value = proplists:get_value(Key, Opts, ReuseSessionFun), - validate_option(Key, Value), - Value; -handle_option(fallback = Key, Opts, _Default, Role, _Host, _Version) -> - Value = proplists:get_value(Key, Opts, default_option_role(client, false, Role)), - assert_role(client_only, Role, Key, Value), - validate_option(Key, Value); -handle_option(client_renegotiation = Key, Opts, _Default, Role, _Host, _Version) -> - Value = proplists:get_value(Key, Opts, default_option_role(server, true, Role)), - assert_role(server_only, Role, Key, Value), - validate_option(Key, Value); -handle_option(honor_cipher_order = Key, Opts, _Default, Role, _Host, _Version) -> - Value = proplists:get_value(Key, Opts, default_option_role(server, false, Role)), - assert_role(server_only, Role, Key, Value), - validate_option(Key, Value); -handle_option(honor_ecc_order = Key, Opts, _Default, Role, _Host, _Version) -> - Value = proplists:get_value(Key, Opts, default_option_role(server, false, Role)), - assert_role(server_only, Role, Key, Value), - validate_option(Key, Value); -handle_option(next_protocol_selector = _Key, Opts, _Default, _Role, _Host, _Version) -> - make_next_protocol_selector( - handle_option(client_preferred_next_protocols, Opts, undefined, undefined, undefined, undefined)); -handle_option(server_name_indication = Key, Opts, _Default, Role, Host, _Version) -> - Default = default_option_role(client, server_name_indication_default(Host), Role), - validate_option(Key, proplists:get_value(Key, Opts, Default)); -handle_option(OptionName, Opts, Default, _Role, _Host, _Version) -> - validate_option(OptionName, - proplists:get_value(OptionName, Opts, Default)). + OptionsMap#{Option => default_value(Option, Rules)} +end; +handle_option(server_name_indication = Option, unbound, OptionsMap, #{host := Host, + role := Role}) -> + Value = default_option_role(client, server_name_indication_default(Host), Role), + OptionsMap#{Option => Value}; +handle_option(server_name_indication = Option, Value0, OptionsMap, _Env) -> + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(signature_algs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{role := Role}) -> + Value = + handle_hashsigns_option( + default_option_role_sign_algs( + server, + tls_v1:default_signature_algs(HighestVersion), + Role, + HighestVersion), + tls_version(HighestVersion)), + OptionsMap#{Option => Value}; +handle_option(signature_algs = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) -> + Value = handle_hashsigns_option(Value0, tls_version(HighestVersion)), + OptionsMap#{Option => Value}; +handle_option(signature_algs_cert = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, _Env) -> + %% Do not send by default + Value = handle_signature_algorithms_option(undefined, tls_version(HighestVersion)), + OptionsMap#{Option => Value}; +handle_option(signature_algs_cert = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) -> + Value = handle_signature_algorithms_option(Value0, tls_version(HighestVersion)), + OptionsMap#{Option => Value}; +handle_option(sni_fun = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = default_value(Option, Rules), + OptionsMap#{Option => Value}; +handle_option(sni_fun = Option, Value0, OptionsMap, _Env) -> + validate_option(Option, Value0), + OptHosts = maps:get(sni_hosts, OptionsMap, undefined), + Value = + case {Value0, OptHosts} of + {undefined, _} -> + Value0; + {_, []} -> + Value0; + _ -> + throw({error, {conflict_options, [sni_fun, sni_hosts]}}) + end, + OptionsMap#{Option => Value}; +handle_option(supported_groups = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) -> + Value = handle_supported_groups_option(groups(default), HighestVersion), + OptionsMap#{Option => Value}; +handle_option(supported_groups = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) -> + Value = handle_supported_groups_option(Value0, HighestVersion), + OptionsMap#{Option => Value}; +handle_option(verify = Option, unbound, OptionsMap, #{rules := Rules}) -> + handle_verify_option(default_value(Option, Rules), OptionsMap); +handle_option(verify = _Option, Value, OptionsMap, _Env) -> + handle_verify_option(Value, OptionsMap); + +handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, #{rules := Rules}) + when Verify =:= verify_none orelse + Verify =:= 0 -> + OptionsMap#{Option => default_value(Option, Rules)}; +handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, _Env) + when Verify =:= verify_peer orelse + Verify =:= 1 orelse + Verify =:= 2 -> + OptionsMap#{Option => undefined}; +handle_option(verify_fun = Option, Value0, OptionsMap, _Env) -> + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(versions = Option, unbound, #{protocol := Protocol} = OptionsMap, _Env) -> + RecordCb = record_cb(Protocol), + Vsns0 = RecordCb:supported_protocol_versions(), + Value = lists:sort(fun RecordCb:is_higher/2, Vsns0), + OptionsMap#{Option => Value}; +handle_option(versions = Option, Vsns0, #{protocol := Protocol} = OptionsMap, _Env) -> + validate_option(versions, Vsns0), + RecordCb = record_cb(Protocol), + Vsns1 = [RecordCb:protocol_version(Vsn) || Vsn <- Vsns0], + Value = lists:sort(fun RecordCb:is_higher/2, Vsns1), + OptionsMap#{Option => Value}; +%% Special options +handle_option(cb_info = Option, unbound, #{protocol := Protocol} = OptionsMap, _Env) -> + Default = default_cb_info(Protocol), + validate_option(Option, Default), + Value = handle_cb_info(Default), + OptionsMap#{Option => Value}; +handle_option(cb_info = Option, Value0, OptionsMap, _Env) -> + validate_option(Option, Value0), + Value = handle_cb_info(Value0), + OptionsMap#{Option => Value}; +%% Generic case +handle_option(Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(Option, Value0, OptionsMap, _Env) -> + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}. + +handle_option_cb_info(Options, Protocol) -> + Value = proplists:get_value(cb_info, Options, default_cb_info(Protocol)), + #{cb_info := CbInfo} = handle_option(cb_info, Value, #{protocol => Protocol}, #{}), + CbInfo. + + +maybe_map_key_internal(client_preferred_next_protocols) -> + next_protocol_selector; +maybe_map_key_internal(K) -> + K. + + +maybe_map_key_external(next_protocol_selector) -> + client_preferred_next_protocols; +maybe_map_key_external(K) -> + K. + + +check_dependencies(K, OptionsMap, Env) -> + Rules = maps:get(rules, Env), + Deps = get_dependencies(K, Rules), + case Deps of + [] -> + true; + L -> + option_already_defined(K,OptionsMap) orelse + dependecies_already_defined(L, OptionsMap) + end. + + +%% Handle options that are not present in the map +get_dependencies(K, _) when K =:= cb_info orelse K =:= log_alert-> + []; +get_dependencies(K, Rules) -> + {_, Deps} = maps:get(K, Rules), + Deps. + + +option_already_defined(K, Map) -> + maps:get(K, Map, unbound) =/= unbound. + + +dependecies_already_defined(L, OptionsMap) -> + Fun = fun (E) -> option_already_defined(E, OptionsMap) end, + lists:all(Fun, L). + + +expand_options(Opts0, Rules) -> + Opts1 = proplists:expand([{binary, [{mode, binary}]}, + {list, [{mode, list}]}], Opts0), + assert_proplist(Opts1), + + %% Remove depricated ssl_imp option + Opts = proplists:delete(ssl_imp, Opts1), + 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 + + SslOpts = {Opts -- SockOpts, [], length(Opts -- SockOpts)}, + {SslOpts, SockOpts}. + + +add_missing_options({L0, S, _C}, Rules) -> + Fun = fun(K0, Acc) -> + K = maybe_map_key_external(K0), + case proplists:is_defined(K, Acc) of + true -> + Acc; + false -> + Default = unbound, + [{K, Default}|Acc] + end + end, + AllOpts = maps:keys(Rules), + L = lists:foldl(Fun, L0, AllOpts), + {L, S, length(L)}. + + +default_value(Key, Rules) -> + {Default, _} = maps:get(Key, Rules, {undefined, []}), + Default. assert_role(client_only, client, _, _) -> @@ -1961,23 +2068,45 @@ validate_option(handshake, full = Value) -> Value; validate_option(customize_hostname_check, Value) when is_list(Value) -> Value; -validate_option(cb_info, {V1, V2, V3, V4}) when is_atom(V1), - is_atom(V2), - is_atom(V3), - is_atom(V4) +validate_option(cb_info, {V1, V2, V3, V4} = Value) when is_atom(V1), + is_atom(V2), + is_atom(V3), + is_atom(V4) -> - true; -validate_option(cb_info, {V1, V2, V3, V4, V5}) when is_atom(V1), - is_atom(V2), - is_atom(V3), - is_atom(V4), - is_atom(V5) + Value; +validate_option(cb_info, {V1, V2, V3, V4, V5} = Value) when is_atom(V1), + is_atom(V2), + is_atom(V3), + is_atom(V4), + is_atom(V5) -> - true; -validate_option(cb_info, _) -> - false; + Value; +validate_option(use_ticket, Value) when is_list(Value) -> + Value; +validate_option(session_tickets, Value) when Value =:= disabled orelse + Value =:= enabled orelse + Value =:= auto orelse + Value =:= stateless orelse + Value =:= stateful -> + Value; +validate_option(anti_replay, '10k') -> + %% n = 10000 + %% p = 0.030003564 (1 in 33) + %% m = 72985 (8.91KiB) + %% k = 5 + {10, 5, 72985}; +validate_option(anti_replay, '100k') -> + %% n = 100000 + %% p = 0.03000428 (1 in 33) + %% m = 729845 (89.09KiB) + %% k = 5 + {10, 5, 729845}; +validate_option(anti_replay, Value) when (is_tuple(Value) andalso + tuple_size(Value) =:= 3) -> + Value; validate_option(Opt, undefined = Value) -> - case lists:member(Opt, ?SSL_OPTIONS) of + AllOpts = maps:keys(?RULES), + case lists:member(Opt, AllOpts) of true -> Value; false -> @@ -1986,9 +2115,9 @@ validate_option(Opt, undefined = Value) -> validate_option(Opt, Value) -> throw({error, {options, {Opt, Value}}}). -handle_cb_info({V1, V2, V3, V4}, {_,_,_,_,_}) -> +handle_cb_info({V1, V2, V3, V4}) -> {V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "_passive")}; -handle_cb_info(CbInfo, _) -> +handle_cb_info(CbInfo) -> CbInfo. handle_hashsigns_option(Value, Version) when is_list(Value) @@ -2275,159 +2404,22 @@ assert_proplist([inet6 | Rest]) -> assert_proplist([Value | _]) -> throw({option_not_a_key_value_tuple, Value}). -new_ssl_options([], #{} = Opts, _) -> - Opts; -new_ssl_options([{verify_client_once, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{verify_client_once => - validate_option(verify_client_once, Value)}, RecordCB); -new_ssl_options([{depth, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{depth => validate_option(depth, Value)}, RecordCB); -new_ssl_options([{cert, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{cert => validate_option(cert, Value)}, RecordCB); -new_ssl_options([{certfile, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{certfile => validate_option(certfile, Value)}, RecordCB); -new_ssl_options([{key, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{key => validate_option(key, Value)}, RecordCB); -new_ssl_options([{keyfile, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{keyfile => validate_option(keyfile, Value)}, RecordCB); -new_ssl_options([{password, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{password => validate_option(password, Value)}, RecordCB); -new_ssl_options([{dh, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{dh => validate_option(dh, Value)}, RecordCB); -new_ssl_options([{dhfile, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{dhfile => validate_option(dhfile, Value)}, RecordCB); -new_ssl_options([{user_lookup_fun, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{user_lookup_fun => validate_option(user_lookup_fun, Value)}, RecordCB); -new_ssl_options([{psk_identity, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{psk_identity => validate_option(psk_identity, Value)}, RecordCB); -new_ssl_options([{srp_identity, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{srp_identity => validate_option(srp_identity, Value)}, RecordCB); -new_ssl_options([{ciphers, Value} | Rest], #{versions := Versions} = Opts, RecordCB) -> - Ciphers = handle_cipher_option(Value, RecordCB:highest_protocol_version(Versions)), - new_ssl_options(Rest, Opts#{ciphers => Ciphers}, RecordCB); -new_ssl_options([{reuse_session, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{reuse_session => validate_option(reuse_session, Value)}, RecordCB); -new_ssl_options([{reuse_sessions, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{reuse_sessions => validate_option(reuse_sessions, Value)}, RecordCB); -new_ssl_options([{ssl_imp, _Value} | Rest], #{} = Opts, RecordCB) -> %% Not used backwards compatibility - new_ssl_options(Rest, Opts, RecordCB); -new_ssl_options([{renegotiate_at, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{renegotiate_at => validate_option(renegotiate_at, Value)}, RecordCB); -new_ssl_options([{secure_renegotiate, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{secure_renegotiate => validate_option(secure_renegotiate, Value)}, RecordCB); -new_ssl_options([{client_renegotiation, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{client_renegotiation => validate_option(client_renegotiation, Value)}, RecordCB); -new_ssl_options([{hibernate_after, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{hibernate_after => validate_option(hibernate_after, Value)}, RecordCB); -new_ssl_options([{alpn_advertised_protocols, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{alpn_advertised_protocols => validate_option(alpn_advertised_protocols, Value)}, - RecordCB); -new_ssl_options([{alpn_preferred_protocols, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{alpn_preferred_protocols => validate_option(alpn_preferred_protocols, Value)}, - RecordCB); -new_ssl_options([{next_protocols_advertised, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{next_protocols_advertised => validate_option(next_protocols_advertised, Value)}, - RecordCB); -new_ssl_options([{client_preferred_next_protocols, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, - Opts#{next_protocol_selector => - make_next_protocol_selector(validate_option(client_preferred_next_protocols, Value))}, - RecordCB); -new_ssl_options([{log_alert, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{log_level => validate_option(log_alert, Value)}, RecordCB); -new_ssl_options([{log_level, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{log_level => validate_option(log_level, Value)}, RecordCB); -new_ssl_options([{server_name_indication, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{server_name_indication => validate_option(server_name_indication, Value)}, RecordCB); -new_ssl_options([{honor_cipher_order, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{honor_cipher_order => validate_option(honor_cipher_order, Value)}, RecordCB); -new_ssl_options([{honor_ecc_order, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#{honor_ecc_order => validate_option(honor_ecc_order, Value)}, RecordCB); -new_ssl_options([{eccs, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, - Opts#{eccs => handle_eccs_option(Value, RecordCB:highest_protocol_version()) - }, - RecordCB); -new_ssl_options([{supported_groups, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, - Opts#{supported_groups => - handle_supported_groups_option(Value, RecordCB:highest_protocol_version()) - }, - RecordCB); -new_ssl_options([{signature_algs, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options(Rest, - Opts#{signature_algs => - handle_hashsigns_option(Value, - tls_version(RecordCB:highest_protocol_version()))}, - RecordCB); -new_ssl_options([{signature_algs_cert, Value} | Rest], #{} = Opts, RecordCB) -> - new_ssl_options( - Rest, - Opts#{signature_algs_cert => - handle_signature_algorithms_option( - Value, - tls_version(RecordCB:highest_protocol_version()))}, - RecordCB); -new_ssl_options([{protocol, dtls = Value} | Rest], #{} = Opts, dtls_record = RecordCB) -> - new_ssl_options(Rest, Opts#{protocol => Value}, RecordCB); -new_ssl_options([{protocol, tls = Value} | Rest], #{} = Opts, tls_record = RecordCB) -> - new_ssl_options(Rest, Opts#{protocol => Value}, RecordCB); -new_ssl_options([{Key, Value} | _Rest], #{}, _) -> - throw({error, {options, {Key, Value}}}). - - -handle_verify_options(Opts, CaCerts) -> - DefaultVerifyNoneFun = - {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, []}, - VerifyNoneFun = handle_option(verify_fun, Opts, DefaultVerifyNoneFun), - - UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false), - UserVerifyFun = handle_option(verify_fun, Opts, undefined), - - PartialChainHanlder = handle_option(partial_chain, Opts, - fun(_) -> unknown_ca end), - - VerifyClientOnce = handle_option(verify_client_once, Opts, false), - - %% Handle 0, 1, 2 for backwards compatibility - case proplists:get_value(verify, Opts, verify_none) of - 0 -> - {verify_none, false, - ca_cert_default(verify_none, VerifyNoneFun, CaCerts), - VerifyNoneFun, PartialChainHanlder, VerifyClientOnce}; - 1 -> - {verify_peer, false, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), - UserVerifyFun, PartialChainHanlder, VerifyClientOnce}; - 2 -> - {verify_peer, true, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), - UserVerifyFun, PartialChainHanlder, VerifyClientOnce}; - verify_none -> - {verify_none, false, - ca_cert_default(verify_none, VerifyNoneFun, CaCerts), - VerifyNoneFun, PartialChainHanlder, VerifyClientOnce}; - verify_peer -> - {verify_peer, UserFailIfNoPeerCert, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), - UserVerifyFun, PartialChainHanlder, VerifyClientOnce}; - Value -> - throw({error, {options, {verify, Value}}}) - end. + +handle_verify_option(verify_none, #{fail_if_no_peer_cert := _FailIfNoPeerCert} = OptionsMap) -> + OptionsMap#{verify => verify_none, + fail_if_no_peer_cert => false}; +handle_verify_option(verify_peer, #{fail_if_no_peer_cert := FailIfNoPeerCert} = OptionsMap) -> + OptionsMap#{verify => verify_peer, + fail_if_no_peer_cert => FailIfNoPeerCert}; +%% Handle 0, 1, 2 for backwards compatibility +handle_verify_option(0, OptionsMap) -> + handle_verify_option(verify_none, OptionsMap); +handle_verify_option(1, OptionsMap) -> + handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => false}); +handle_verify_option(2, OptionsMap) -> + handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => true}); +handle_verify_option(Value, _) -> + throw({error, {options, {verify, Value}}}). %% Added to handle default values for signature_algs in TLS 1.3 default_option_role_sign_algs(_, Value, _, Version) when Version >= {3,4} -> @@ -2464,7 +2456,7 @@ server_name_indication_default(_) -> undefined. -reject_alpn_next_prot_options(Opts) -> +reject_alpn_next_prot_options({Opts,_,_}) -> AlpnNextOpts = [alpn_advertised_protocols, alpn_preferred_protocols, next_protocols_advertised, @@ -2486,10 +2478,3 @@ add_filter(undefined, Filters) -> Filters; add_filter(Filter, Filters) -> [Filter | Filters]. - -%% Convert the record #ssl_options{} into a map for internal usage -options_to_map(Options) -> - Fields = record_info(fields, ssl_options), - [_Tag| Values] = tuple_to_list(Options), - L = lists:zip(Fields, Values), - maps:from_list(L). |