diff --git a/lib/ssl/src/dtls_client_connection.erl b/lib/ssl/src/dtls_client_connection.erl index ebff84a9ddc..48370689a0d 100644 --- a/lib/ssl/src/dtls_client_connection.erl +++ b/lib/ssl/src/dtls_client_connection.erl @@ -230,12 +230,11 @@ initial_hello({call, From}, {start, {Opts, EmOpts}, Timeout}, ssl_options = OrigSSLOptions, socket_options = SockOpts} = State0) -> try - SslOpts = ssl:update_options(Opts, Role, OrigSSLOptions), + SslOpts = ssl_config:update_options(Opts, Role, OrigSSLOptions), State = ssl_gen_statem:ssl_config(SslOpts, Role, State0), initial_hello({call, From}, {start, Timeout}, - State#state{ssl_options = SslOpts, - socket_options = - ssl_config:new_emulated(EmOpts, SockOpts)}) + State#state{socket_options = + ssl_config:new_emulated(EmOpts, SockOpts)}) catch throw:Error -> {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0} end; diff --git a/lib/ssl/src/dtls_server_connection.erl b/lib/ssl/src/dtls_server_connection.erl index b3224c53737..d2c75944813 100644 --- a/lib/ssl/src/dtls_server_connection.erl +++ b/lib/ssl/src/dtls_server_connection.erl @@ -196,12 +196,11 @@ initial_hello({call, From}, {start, {Opts, EmOpts}, Timeout}, ssl_options = OrigSSLOptions, socket_options = SockOpts} = State0) -> try - SslOpts = ssl:update_options(Opts, Role, OrigSSLOptions), + SslOpts = ssl_config:update_options(Opts, Role, OrigSSLOptions), State = ssl_gen_statem:ssl_config(SslOpts, Role, State0), initial_hello({call, From}, {start, Timeout}, - State#state{ssl_options = SslOpts, - socket_options = - ssl_config:new_emulated(EmOpts, SockOpts)}) + State#state{socket_options = + ssl_config:new_emulated(EmOpts, SockOpts)}) catch throw:Error -> {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0} end; diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 249d2c6194f..815084ff88b 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -138,9 +138,7 @@ Special Erlang node configuration for the application can be found in connection_information/1, connection_information/2]). %% Misc --export([handle_options/3, - update_options/3, - tls_version/1, +-export([tls_version/1, suite_to_str/1, suite_to_openssl_str/1, str_to_suite/1]). @@ -2171,13 +2169,11 @@ connect(Host, Port, TLSOptions, infinity). Reason :: closed | timeout | {options, any()} | error_alert() | reason(). -connect(TCPSocket, TLSOptions0, Timeout) - when is_list(TLSOptions0), ?IS_TIMEOUT(Timeout) -> +connect(TCPSocket, TLSOptions, Timeout) + when is_list(TLSOptions), ?IS_TIMEOUT(Timeout) -> try - CbInfo = handle_option_cb_info(TLSOptions0, tls), - Transport = element(1, CbInfo), - {ok, Config} = handle_options(Transport, TCPSocket, TLSOptions0, client, undefined), + {ok, Config} = ssl_config:handle_options(TCPSocket, TLSOptions, client, undefined), tls_socket:upgrade(TCPSocket, Config, Timeout) catch _:{error, Reason} -> @@ -2237,7 +2233,7 @@ owning the SSL socket will receive messages of type connect(Host, Port, Options, Timeout) when is_integer(Port), is_list(Options), ?IS_TIMEOUT(Timeout) -> try - {ok, Config} = handle_options(Options, client, Host), + {ok, Config} = ssl_config:handle_options(Options, client, Host), case Config#config.connection_cb of tls_gen_connection -> tls_socket:connect(Host,Port,Config,Timeout); @@ -2264,7 +2260,7 @@ listen(_Port, []) -> listen(Port, Options0) when is_integer(Port), is_list(Options0) -> try - {ok, Config} = handle_options(Options0, server, undefined), + {ok, Config} = ssl_config:handle_options(Options0, server, undefined), do_listen(Port, Config, Config#config.connection_cb) catch Error = {error, _} -> @@ -2447,12 +2443,11 @@ handshake(#sslsocket{socket_handle = {Controller,_}, connection_cb = dtls_gen_co end; handshake(Socket, SslOptions, Timeout) when is_list(SslOptions), ?IS_TIMEOUT(Timeout) -> - try - CbInfo = handle_option_cb_info(SslOptions, tls), - Transport = element(1, CbInfo), + try ConnetionCb = connection_cb(SslOptions), {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} = - handle_options(Transport, Socket, SslOptions, server, undefined), + ssl_config:handle_options(Socket, SslOptions, server, undefined), + Transport = element(1, CbInfo), ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()), {ok, Port} = tls_socket:port(Transport, Socket), {ok, SessionIdHandle} = tls_socket:session_id_tracker(ssl_unknown_listener, SslOpts), @@ -3724,1368 +3719,6 @@ do_listen(Port, #config{transport_info = Info} = Config, tls_gen_connection) -> do_listen(Port, Config, dtls_gen_connection) -> dtls_socket:listen(Port, Config). -ssl_options() -> - [ - alpn_advertised_protocols, alpn_preferred_protocols, - anti_replay, - beast_mitigation, - cacertfile, cacerts, - cert, certs_keys,certfile, - certificate_authorities, - ciphers, - client_renegotiation, - cookie, - crl_cache, crl_check, - customize_hostname_check, - depth, - dh, dhfile, - - early_data, - eccs, - erl_dist, - fail_if_no_peer_cert, - fallback, - handshake, - hibernate_after, - honor_cipher_order, honor_ecc_order, - keep_secrets, - key, keyfile, - key_update_at, - ktls, - - log_level, - max_handshake_size, - middlebox_comp_mode, - max_fragment_length, - next_protocol_selector, next_protocols_advertised, - stapling, - padding_check, - partial_chain, - password, - protocol, - psk_identity, - receiver_spawn_opts, - renegotiate_at, - reuse_session, reuse_sessions, - - secure_renegotiate, - sender_spawn_opts, - server_name_indication, - session_tickets, - stateless_tickets_seed, - signature_algs, signature_algs_cert, - sni_fun, - sni_hosts, - srp_identity, - supported_groups, - use_ticket, - use_srtp, - user_lookup_fun, - verify, verify_fun, cert_policy_opts, - allow_any_ca_purpose, - versions - ]. - -%% Handle ssl options at handshake, handshake_continue --doc false. --spec update_options([any()], client | server, map()) -> map(). -update_options(Opts, Role, InheritedSslOpts) when is_map(InheritedSslOpts) -> - {UserSslOpts, _} = split_options(Opts, ssl_options()), - Env = #{role => Role, validate_certs_or_anon_ciphers => Role == server}, - process_options(UserSslOpts, InheritedSslOpts, Env). - -process_options(UserSslOpts, SslOpts0, Env) -> - %% Reverse option list so we get the last set option if set twice, - %% users depend on it. - UserSslOptsMap = proplists:to_map(lists:reverse(UserSslOpts)), - SslOpts1 = opt_protocol_versions(UserSslOptsMap, SslOpts0, Env), - SslOpts2 = opt_verification(UserSslOptsMap, SslOpts1, Env), - SslOpts3 = opt_certs(UserSslOptsMap, SslOpts2, Env), - SslOpts4 = opt_tickets(UserSslOptsMap, SslOpts3, Env), - SslOpts5 = opt_stapling(UserSslOptsMap, SslOpts4, Env), - SslOpts6 = opt_sni(UserSslOptsMap, SslOpts5, Env), - SslOpts7 = opt_signature_algs(UserSslOptsMap, SslOpts6, Env), - SslOpts8 = opt_alpn(UserSslOptsMap, SslOpts7, Env), - SslOpts9 = opt_mitigation(UserSslOptsMap, SslOpts8, Env), - SslOpts10 = opt_server(UserSslOptsMap, SslOpts9, Env), - SslOpts11 = opt_client(UserSslOptsMap, SslOpts10, Env), - SslOpts12 = opt_renegotiate(UserSslOptsMap, SslOpts11, Env), - SslOpts13 = opt_reuse_sessions(UserSslOptsMap, SslOpts12, Env), - SslOpts14 = opt_identity(UserSslOptsMap, SslOpts13, Env), - SslOpts15 = opt_supported_groups(UserSslOptsMap, SslOpts14, Env), - SslOpts16 = opt_crl(UserSslOptsMap, SslOpts15, Env), - SslOpts17 = opt_handshake(UserSslOptsMap, SslOpts16, Env), - SslOpts18 = opt_use_srtp(UserSslOptsMap, SslOpts17, Env), - SslOpts = opt_process(UserSslOptsMap, SslOpts18, Env), - validate_server_cert_opts(SslOpts, Env), - SslOpts. - --doc false. --spec handle_options([any()], client | server, undefined|host()) -> {ok, #config{}}. -handle_options(Opts, Role, Host) -> - handle_options(undefined, undefined, Opts, Role, Host). - -%% Handle all options in listen, connect and handshake -handle_options(Transport, Socket, Opts0, Role, Host) -> - {UserSslOptsList, SockOpts0} = split_options(Opts0, ssl_options()), - NeedValidate = not (Socket == undefined) andalso Role =:= server, %% handshake options - Env = #{role => Role, host => Host, - validate_certs_or_anon_ciphers => NeedValidate - }, - SslOpts = process_options(UserSslOptsList, #{}, Env), - - %% Handle special options - #{protocol := Protocol} = SslOpts, - {Sock, Emulated} = emulated_options(Transport, Socket, Protocol, SockOpts0), - 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 - }}. - - -opt_protocol_versions(UserOpts, Opts, Env) -> - {_, PRC} = get_opt_of(protocol, [tls, dtls], tls, UserOpts, Opts), - - LogLevels = [none, all, emergency, alert, critical, error, - warning, notice, info, debug], - - DefaultLevel = case logger:get_module_level(?MODULE) of - [] -> notice; - [{ssl,Level}] -> Level - end, - - {_, LL} = get_opt_of(log_level, LogLevels, DefaultLevel, UserOpts, Opts), - - Opts1 = set_opt_bool(keep_secrets, false, UserOpts, Opts), - - {DistW, Dist} = get_opt_bool(erl_dist, false, UserOpts, Opts1), - option_incompatible(PRC =:= dtls andalso Dist, [{protocol, PRC}, {erl_dist, Dist}]), - Opts2 = set_opt_new(DistW, erl_dist, false, Dist, Opts1), - - {KtlsW, Ktls} = get_opt_bool(ktls, false, UserOpts, Opts1), - option_incompatible(PRC =:= dtls andalso Ktls, [{protocol, PRC}, {ktls, Ktls}]), - Opts3 = set_opt_new(KtlsW, ktls, false, Ktls, Opts2), - - opt_versions(UserOpts, Opts3#{protocol => PRC, log_level => LL}, Env). - -opt_versions(UserOpts, #{protocol := Protocol} = Opts, _Env) -> - Versions = case get_opt(versions, unbound, UserOpts, Opts) of - {default, unbound} -> default_versions(Protocol); - {new, Vs} -> validate_versions(Protocol, Vs); - {old, Vs} -> Vs - end, - - {Where, MCM} = get_opt_bool(middlebox_comp_mode, true, UserOpts, Opts), - assert_version_dep(Where =:= new, middlebox_comp_mode, Versions, ['tlsv1.3']), - Opts1 = set_opt_new(Where, middlebox_comp_mode, true, MCM, Opts), - Opts1#{versions => Versions}. - -default_versions(tls) -> - Vsns0 = tls_record:supported_protocol_versions(), - lists:sort(fun tls_record:is_higher/2, Vsns0); -default_versions(dtls) -> - Vsns0 = dtls_record:supported_protocol_versions(), - lists:sort(fun dtls_record:is_higher/2, Vsns0). - -validate_versions(tls, Vsns0) -> - Validate = - fun(Version) -> - try tls_record:sufficient_crypto_support(Version) of - true -> tls_record:protocol_version_name(Version); - false -> option_error(insufficient_crypto_support, - {Version, {versions, Vsns0}}) - catch error:function_clause -> - option_error(Version, {versions, Vsns0}) - end - end, - Vsns = [Validate(V) || V <- Vsns0], - tls_validate_version_gap(Vsns0), - option_error([] =:= Vsns, versions, Vsns0), - lists:sort(fun tls_record:is_higher/2, Vsns); -validate_versions(dtls, Vsns0) -> - Validate = - fun(Version) -> - try tls_record:sufficient_crypto_support( - dtls_v1:corresponding_tls_version( - dtls_record:protocol_version_name(Version))) of - true -> dtls_record:protocol_version_name(Version); - false-> option_error(insufficient_crypto_support, - {Version, {versions, Vsns0}}) - catch error:function_clause -> - option_error(Version, {versions, Vsns0}) - end - end, - Vsns = [Validate(V) || V <- Vsns0], - option_error([] =:= Vsns, versions, Vsns0), - lists:sort(fun dtls_record:is_higher/2, Vsns). - -opt_verification(UserOpts, Opts0, #{role := Role} = Env) -> - {Verify, Opts1} = - case get_opt_of(verify, [verify_none, verify_peer], default_verify(Role), UserOpts, Opts0) of - {old, Val} -> - {Val, Opts0}; - {_, verify_none} -> - {verify_none, Opts0#{verify => verify_none, verify_fun => {none_verify_fun(), []}}}; - {_, verify_peer} -> - %% If 'verify' is changed from verify_none to verify_peer, (via update_options/3) - %% the 'verify_fun' must also be changed to undefined. - %% i.e remove verify_none fun - Temp = Opts0#{verify => verify_peer, verify_fun => undefined}, - {verify_peer, maps:remove(fail_if_no_peer_cert, Temp)} - end, - Opts2 = opt_cacerts(UserOpts, Opts1, Env), - {_, PartialChain} = get_opt_fun(partial_chain, 1, fun(_) -> unknown_ca end, UserOpts, Opts2), - - DefFailNoPeer = Role =:= server andalso Verify =:= verify_peer, - {_, FailNoPeerCert} = get_opt_bool(fail_if_no_peer_cert, DefFailNoPeer, UserOpts, Opts2), - assert_server_only(Role, FailNoPeerCert, fail_if_no_peer_cert), - option_incompatible(FailNoPeerCert andalso Verify =:= verify_none, - [{verify, verify_none}, {fail_if_no_peer_cert, true}]), - - Opts3 = set_opt_int(depth, 0, 255, ?DEFAULT_DEPTH, UserOpts, Opts2), - - Opts4 = case Role of - client -> - opt_verify_fun(UserOpts, Opts3#{partial_chain => PartialChain}, - Env); - server -> - opt_verify_fun(UserOpts, Opts3#{partial_chain => PartialChain, - fail_if_no_peer_cert => FailNoPeerCert}, - Env) - end, - Opts = opt_policies(UserOpts, Opts4), - opt_extend_keyusage(UserOpts, Opts). - -default_verify(client) -> - %% Server authenication is by default requiered - verify_peer; -default_verify(server) -> - %% Client certification is an optional part of the protocol - verify_none. - -opt_verify_fun(UserOpts, Opts, _Env) -> - %%DefVerifyNoneFun = {default_verify_fun(), []}, - VerifyFun = case get_opt(verify_fun, undefined, UserOpts, Opts) of - {_, {F,_} = FA} when is_function(F, 3); is_function(F, 4) -> - FA; - {_, UserFun} when is_function(UserFun, 1) -> - {convert_verify_fun(), UserFun}; - {_, undefined} -> - undefined; - {_, Value} -> - option_error(verify_fun, Value) - end, - Opts#{verify_fun => VerifyFun}. - -none_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. - -convert_verify_fun() -> - fun(_,{bad_cert, _} = Reason, OldFun) -> - case OldFun([Reason]) of - true -> {valid, OldFun}; - false -> {fail, Reason} - end; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, UserState}; - (_, valid_peer, UserState) -> - {valid, UserState} - end. - -opt_policies(UserOpts, Opts) -> - case get_opt(cert_policy_opts, [], UserOpts, Opts) of - {default, []} -> - Opts#{cert_policy_opts => []}; - {old, POpts} -> - Opts#{cert_policy_opts => POpts}; - {_, POpts} -> - validate_policy_opts(POpts), - Opts#{cert_policy_opts => POpts} - end. - -opt_extend_keyusage(UserOpts, Opts) -> - case get_opt_bool(allow_any_ca_purpose, false, UserOpts, Opts) of - {default, Value} -> - Opts#{allow_any_ca_purpose => Value}; - {old, _OldValue} -> - Opts; - {new, NewValue} -> - Opts#{allow_any_ca_purpose => NewValue} - end. - -validate_policy_opts([]) -> - true; -validate_policy_opts([{policy_set, OidList} | Rest]) when is_list(OidList) -> - validate_policy_opts(Rest); -validate_policy_opts([{Opt, Bool} | Rest]) when Opt == explicit_policy; - Opt == inhibit_policy_mapping; - Opt == inhibit_any_policy -> - case is_boolean(Bool) of - true -> - validate_policy_opts(Rest); - false -> - option_error(cert_policy_opts, {Opt, Bool}) - end; -validate_policy_opts([Opt| _]) -> - option_error(cert_policy_opts, Opt). - -opt_certs(UserOpts, #{log_level := LogLevel, versions := Versions} = Opts0, Env) -> - case get_opt_list(certs_keys, [], UserOpts, Opts0) of - {Where, []} when Where =/= new -> - opt_old_certs(UserOpts, #{}, Opts0, Env); - {old, [CertKey]} -> - opt_old_certs(UserOpts, CertKey, Opts0, Env); - {Where, CKs0} when is_list(CKs0) -> - warn_override(Where, UserOpts, certs_keys, [cert,certfile,key,keyfile,password], LogLevel), - CKs = lists:foldl(fun(CK0, Acc) -> - CK = check_cert_key(Versions, CK0, #{}, LogLevel), - case maps:size(CK) =:= 0 of - true -> - Acc; - false -> - [CK|Acc] - end - end, [], CKs0), - Opts0#{certs_keys => lists:reverse(CKs)} - end. - -opt_old_certs(UserOpts, CertKeys, #{log_level := LogLevel, versions := Versions}=SSLOpts, _Env) -> - CK = check_cert_key(Versions, UserOpts, CertKeys, LogLevel), - case maps:size(CK) =:= 0 of - true -> - SSLOpts#{certs_keys => []}; - false -> - SSLOpts#{certs_keys => [CK]} - end. - -check_cert_key(Versions, UserOpts, CertKeys, LogLevel) -> - CertKeys0 = case get_opt(cert, undefined, UserOpts, CertKeys) of - {Where, Cert} when is_binary(Cert) -> - warn_override(Where, UserOpts, cert, [certfile], LogLevel), - CertKeys#{cert => [Cert]}; - {Where, [C0|_] = Certs} when is_binary(C0) -> - warn_override(Where, UserOpts, cert, [certfile], LogLevel), - CertKeys#{cert => Certs}; - {new, Err0} -> - option_error(cert, Err0); - {_, undefined} -> - case get_opt_file(certfile, unbound, UserOpts, CertKeys) of - {default, unbound} -> CertKeys; - {_, CertFile} -> CertKeys#{certfile => CertFile} - end - end, - - CertKeys1 = case get_opt(key, undefined, UserOpts, CertKeys) of - {_, undefined} -> - case get_opt_file(keyfile, <<>>, UserOpts, CertKeys) of - {new, KeyFile} -> - CertKeys0#{keyfile => KeyFile}; - {_, <<>>} -> - case maps:get(certfile, CertKeys0, unbound) of - unbound -> CertKeys0; - CF -> CertKeys0#{keyfile => CF} - end; - {old, _} -> - CertKeys0 - end; - {_, {KF, K0} = Key} - when is_binary(K0), KF =:= rsa; KF =:= dsa; - KF == 'RSAPrivateKey'; KF == 'DSAPrivateKey'; - KF == 'ECPrivateKey'; KF == 'PrivateKeyInfo' -> - CertKeys0#{key => Key}; - {_, #{engine := _, key_id := _, algorithm := Algo} = Key} -> - check_key_algo_version_dep(Versions, Algo), - CertKeys0#{key => Key}; - {_, #{sign_fun := _, algorithm := Algo} = Key} -> - check_key_algo_version_dep(Versions, Algo), - check_key_legacy_version_dep(Versions, Key, Algo), - CertKeys0#{key => Key}; - {_, #{encrypt_fun := _, algorithm := rsa} = Key} -> - check_key_legacy_version_dep(Versions, Key), - CertKeys0#{key => Key}; - {new, Err1} -> - option_error(key, Err1) - end, - - CertKeys2 = case get_opt(password, unbound, UserOpts,CertKeys) of - {default, _} -> CertKeys1; - {_, Pwd} when is_binary(Pwd); is_list(Pwd) -> - CertKeys1#{password => fun() -> Pwd end}; - {_, Pwd} when is_function(Pwd, 0) -> - CertKeys1#{password => Pwd}; - {_, Err2} -> - option_error(password, Err2) - end, - CertKeys2. - -check_key_algo_version_dep(Versions, eddsa) -> - assert_version_dep(key, Versions, ['tlsv1.3']); -check_key_algo_version_dep(Versions, rsa_pss_pss) -> - assert_version_dep(key, Versions, ['tlsv1.3', 'tlsv1.2']); -check_key_algo_version_dep(Versions, dsa) -> - assert_version_dep(key, Versions, ['tlsv1.2', 'tlsv1.1', 'tlsv1']); -check_key_algo_version_dep(_,_) -> - true. - -check_key_legacy_version_dep(Versions, Key, rsa) -> - check_key_legacy_version_dep(Versions, Key); -check_key_legacy_version_dep(_,_,_) -> - true. - -check_key_legacy_version_dep(Versions, Key) -> - EncryptFun = maps:get(encrypt_fun, Key, undefined), - case EncryptFun of - undefined -> - assert_version_dep(key, Versions, ['tlsv1.3', 'tlsv1.2']); - _ -> - assert_version_dep(key, Versions, ['tlsv1.1', 'tlsv1']) - end. - -opt_cacerts(UserOpts, #{verify := Verify, log_level := LogLevel, versions := Versions} = Opts, - #{role := Role}) -> - {_, CaCerts} = get_opt_list(cacerts, undefined, UserOpts, Opts), - - CaCertFile = case get_opt_file(cacertfile, <<>>, UserOpts, Opts) of - {Where1, _FileName} when CaCerts =/= undefined -> - warn_override(Where1, UserOpts, cacerts, [cacertfile], LogLevel), - <<>>; - {new, FileName} -> unambiguous_path(FileName); - {_, FileName} -> FileName - end, - option_incompatible(CaCertFile =:= <<>> andalso CaCerts =:= undefined andalso Verify =:= verify_peer, - [{verify, verify_peer}, {cacerts, undefined}]), - - {Where2, CA} = get_opt_bool(certificate_authorities, Role =:= server, UserOpts, Opts), - case Role of - server -> - assert_version_dep(Where2 =:= new, certificate_authorities, Versions, ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', 'tlsv1']); - client -> - assert_version_dep(Where2 =:= new, certificate_authorities, Versions, ['tlsv1.3']) - end, - Opts1 = set_opt_new(new, cacertfile, <<>>, CaCertFile, Opts), - Opts2 = set_opt_new(Where2, certificate_authorities, Role =:= server, CA, Opts1), - Opts2#{cacerts => CaCerts}. - -opt_tickets(UserOpts, #{versions := Versions} = Opts, #{role := client}) -> - {_, SessionTickets} = get_opt_of(session_tickets, [disabled,manual,auto], disabled, UserOpts, Opts), - assert_version_dep(SessionTickets =/= disabled, session_tickets, Versions, ['tlsv1.3']), - - {_, UseTicket} = get_opt_list(use_ticket, undefined, UserOpts, Opts), - option_error(UseTicket =:= [], use_ticket, UseTicket), - option_incompatible(UseTicket =/= undefined andalso SessionTickets =/= manual, - [{use_ticket, UseTicket}, {session_tickets, SessionTickets}]), - - {_, EarlyData} = get_opt_bin(early_data, undefined, UserOpts, Opts), - option_incompatible(is_binary(EarlyData) andalso SessionTickets =:= disabled, - [early_data, {session_tickets, disabled}]), - option_incompatible(is_binary(EarlyData) andalso SessionTickets =:= manual andalso UseTicket =:= undefined, - [early_data, {session_tickets, manual}, {use_ticket, undefined}]), - - assert_server_only(anti_replay, UserOpts), - assert_server_only(stateless_tickets_seed, UserOpts), - Opts#{session_tickets => SessionTickets, use_ticket => UseTicket, early_data => EarlyData}; -opt_tickets(UserOpts, #{versions := Versions} = Opts, #{role := server}) -> - {_, SessionTickets} = - get_opt_of(session_tickets, - [disabled, stateful, stateless, stateful_with_cert, stateless_with_cert], - disabled, - UserOpts, - Opts), - assert_version_dep(SessionTickets =/= disabled, session_tickets, Versions, ['tlsv1.3']), - - {_, EarlyData} = get_opt_of(early_data, [enabled, disabled], disabled, UserOpts, Opts), - option_incompatible(SessionTickets =:= disabled andalso EarlyData =:= enabled, - [early_data, {session_tickets, disabled}]), - - Stateless = lists:member(SessionTickets, [stateless, stateless_with_cert]), - - AntiReplay = - case get_opt(anti_replay, undefined, UserOpts, Opts) of - {_, undefined} -> undefined; - {_,AR} when not Stateless -> - option_incompatible([{anti_replay, AR}, {session_tickets, SessionTickets}]); - {_,'10k'} -> {10, 5, 72985}; %% n = 10000 p = 0.030003564 (1 in 33) m = 72985 (8.91KiB) k = 5 - {_,'100k'} -> {10, 5, 729845}; %% n = 10000 p = 0.03000428 (1 in 33) m = 729845 (89.09KiB) k = 5 - {_, {_,_,_} = AR} -> AR; - {_, AR} -> option_error(anti_replay, AR) - end, - - {_, STS} = get_opt_bin(stateless_tickets_seed, undefined, UserOpts, Opts), - option_incompatible(STS =/= undefined andalso not Stateless, - [stateless_tickets_seed, {session_tickets, SessionTickets}]), - - assert_client_only(use_ticket, UserOpts), - Opts#{session_tickets => SessionTickets, early_data => EarlyData, - anti_replay => AntiReplay, stateless_tickets_seed => STS}. - -opt_stapling(UserOpts, #{versions := _Versions} = Opts, #{role := client}) -> - {Stapling, Nonce} = - case get_opt(stapling, ?DEFAULT_STAPLING_OPT, UserOpts, Opts) of - {old, StaplingMap} when is_map(StaplingMap) -> - {true, maps:get(ocsp_nonce, StaplingMap, ?DEFAULT_OCSP_NONCE_OPT)}; - {_, staple} -> - {true, ?DEFAULT_OCSP_NONCE_OPT}; - {_, no_staple} -> - {false, ignore}; - {_, Map} when is_map(Map) -> - {true, maps:get(ocsp_nonce, Map, ?DEFAULT_OCSP_NONCE_OPT)}; - {_, Value} -> - option_error(stapling, Value) - end, - case Stapling of - true -> - Opts#{stapling => - #{ocsp_nonce => Nonce}}; - false -> - Opts - end; -opt_stapling(UserOpts, Opts, #{role := server}) -> - assert_client_only(stapling, UserOpts), - Opts. - -opt_sni(UserOpts, #{versions := _Versions} = Opts, #{role := server}) -> - {_, SniHosts} = get_opt_list(sni_hosts, [], UserOpts, Opts), - %% Postpone option checking until all other options are checked FIXME - Check = fun({[_|_], SO}) when is_list(SO) -> - case proplists:get_value(sni_hosts, SO, undefined) of - undefined -> ok; - Recursive -> option_error(sni_hosts, Recursive) - end; - (HostOpts) -> option_error(sni_hosts, HostOpts) - end, - [Check(E) || E <- SniHosts], - - {Where, SniFun0} = get_opt_fun(sni_fun, 1, undefined, UserOpts, Opts), - - option_incompatible(is_function(SniFun0) andalso SniHosts =/= [] andalso Where =:= new, - [sni_fun, sni_hosts]), - assert_client_only(server_name_indication, UserOpts), - - SniFun = case SniFun0 =:= undefined of - true -> fun(Host) -> proplists:get_value(Host, SniHosts) end; - false -> SniFun0 - end, - - Opts#{sni_fun => SniFun}; -opt_sni(UserOpts, #{versions := _Versions} = Opts, #{role := client} = Env) -> - %% RFC 6066, Section 3: Currently, the only server names supported are - %% DNS hostnames - %% case inet_parse:domain(Value) of - %% false -> - %% throw({error, {options, {{Opt, Value}}}}); - %% true -> - %% Value - %% end; - %% - %% But the definition seems very diffuse, so let all strings through - %% and leave it up to public_key to decide... - SNI = case get_opt(server_name_indication, unbound, UserOpts, Opts) of - {_, unbound} -> server_name_indication_default(maps:get(host, Env, undefined)); - {_, [_|_] = SN} -> SN; - {_, disable} -> disable; - {_, SN} -> option_error(server_name_indication, SN) - end, - assert_server_only(sni_fun, UserOpts), - assert_server_only(sni_hosts, UserOpts), - Opts#{server_name_indication => SNI}. - -server_name_indication_default(Host) when is_list(Host) -> - %% SNI should not contain a trailing dot that a hostname may - string:strip(Host, right, $.); -server_name_indication_default(_) -> - undefined. - -opt_signature_algs(UserOpts, #{versions := Versions} = Opts, _Env) -> - [TlsVersion|_] = TlsVsns = [tls_version(V) || V <- Versions], - case ?TLS_GTE(TlsVersion, ?TLS_1_2) of - true -> - opt_signature_algs_valid(UserOpts, Opts, TlsVsns); - false -> - opt_signature_algs_not_valid(UserOpts, Opts) - end. - -opt_signature_algs_valid(UserOpts, #{versions := Versions} = Opts, [TlsVersion|_] = TlsVsns)-> - SAC1 = case get_opt_list(signature_algs_cert, undefined, UserOpts, Opts) of - {new, Schemes} -> - assert_version_dep(signature_algs_cert, Versions, ['tlsv1.2', 'tlsv1.3']), - SAC0 = handle_signature_algorithms_option(Schemes, TlsVersion), - option_error(SAC0 =:= [], no_supported_signature_schemes, - {signature_algs_cert, Schemes}), - SAC0; - {_, Schemes} -> - Schemes - end, - - {SA, SAC2} = - case get_opt_list(signature_algs, undefined, UserOpts, Opts) of - {default, undefined} -> - %% Smooth upgrade path allow rsa_pkcs1_sha1 for signatures_algs_cert - %% by default as long as signature_algs is set to default - DefAlgs0 = tls_v1:default_signature_algs(TlsVsns), - DefAlgs = handle_hashsigns_option(DefAlgs0, TlsVersion), - DSAC0 = case SAC1 of - undefined -> - [default | DefAlgs ++ sha_rsa(TlsVersion)]; - _ -> - SAC1 - end, - {DefAlgs, DSAC0}; - {new, Algs} -> - assert_version_dep(signature_algs, Versions, ['tlsv1.2', 'tlsv1.3']), - SA0 = handle_hashsigns_option(Algs, TlsVersion), - option_error(SA0 =:= [], no_supported_algorithms, {signature_algs, Algs}), - DSAC0 = case SAC1 of - %% If user sets signature_algs, signature_algs_cert default should - %% be undefined. - [default |_] -> - undefined; - SAC1 -> - SAC1 - end, - {SA0, DSAC0}; - {old, Algs} -> - {Algs, SAC1} - end, - Opts#{signature_algs => SA, signature_algs_cert => SAC2}. - -opt_signature_algs_not_valid(UserOpts, #{versions := Versions} = Opts0)-> - Opts = - case get_opt_list(signature_algs, undefined, UserOpts, Opts0) of - {default, undefined} -> - Opts0#{signature_algs => undefined}; - {old, _} -> - Opts0; - _ -> - option_incompatible([signature_algs, {versions, Versions}]) - end, - case get_opt_list(signature_algs_cert, undefined, UserOpts, Opts) of - {default, undefined} -> - Opts#{signature_algs_cert => undefined}; - {old, _} -> - Opts; - _ -> - option_incompatible([signature_algs_cert, {versions, Versions}]) - end. - -sha_rsa(?TLS_1_2) -> - [{sha, rsa}]; -sha_rsa(?TLS_1_3) -> - [rsa_pkcs1_sha1]. - -opt_alpn(UserOpts, #{versions := Versions} = Opts, #{role := server}) -> - {_, APP} = get_opt_list(alpn_preferred_protocols, undefined, UserOpts, Opts), - validate_protocols(is_list(APP), alpn_preferred_protocols, APP), - - {Where, NPA} = get_opt_list(next_protocols_advertised, undefined, UserOpts, Opts), - validate_protocols(is_list(NPA), next_protocols_advertised, NPA), - assert_version_dep(is_list(NPA), next_protocols_advertised, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - - assert_client_only(alpn_advertised_protocols, UserOpts), - assert_client_only(client_preferred_next_protocols, UserOpts), - - Opts1 = set_opt_new(Where, next_protocols_advertised, undefined, NPA, Opts), - Opts1#{alpn_preferred_protocols => APP}; -opt_alpn(UserOpts, #{versions := Versions} = Opts, #{role := client}) -> - {_, AAP} = get_opt_list(alpn_advertised_protocols, undefined, UserOpts, Opts), - validate_protocols(is_list(AAP), alpn_advertised_protocols, AAP), - - {Where, NPS} = case get_opt(client_preferred_next_protocols, undefined, UserOpts, Opts) of - {new, CPNP} -> - assert_version_dep(client_preferred_next_protocols, - Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - {new, make_next_protocol_selector(CPNP)}; - CPNP -> - CPNP - end, - - validate_protocols(is_list(NPS), client_preferred_next_protocols, NPS), - - assert_server_only(alpn_preferred_protocols, UserOpts), - assert_server_only(next_protocols_advertised, UserOpts), - - Opts1 = set_opt_new(Where, next_protocol_selector, undefined, NPS, Opts), - Opts1#{alpn_advertised_protocols => AAP}. - -validate_protocols(false, _Opt, _List) -> ok; -validate_protocols(true, Opt, List) -> - Check = fun(Bin) -> - IsOK = is_binary(Bin) andalso byte_size(Bin) > 0 andalso byte_size(Bin) < 256, - option_error(not IsOK, Opt, {invalid_protocol, Bin}) - end, - lists:foreach(Check, List). - -opt_mitigation(UserOpts, #{versions := Versions} = Opts, _Env) -> - DefBeast = case ?TLS_GT(lists:last(Versions), ?TLS_1_0) of - true -> disabled; - false -> one_n_minus_one - end, - {Where1, BM} = get_opt_of(beast_mitigation, [disabled, one_n_minus_one, zero_n], DefBeast, UserOpts, Opts), - assert_version_dep(Where1 =:= new, beast_mitigation, Versions, ['tlsv1']), - - {Where2, PC} = get_opt_bool(padding_check, true, UserOpts, Opts), - assert_version_dep(Where2 =:= new, padding_check, Versions, ['tlsv1']), - - %% Use 'new' we need to check for non default 'one_n_minus_one' - Opts1 = if - DefBeast =:= one_n_minus_one, BM =:= disabled -> - Opts#{beast_mitigation => BM}; - true -> - set_opt_new(new, beast_mitigation, disabled, BM, Opts) - end, - set_opt_new(Where2, padding_check, true, PC, Opts1). - -opt_server(UserOpts, #{versions := Versions, log_level := LogLevel} = Opts, #{role := server}) -> - {_, ECC} = get_opt_bool(honor_ecc_order, false, UserOpts, Opts), - - {_, Cipher} = get_opt_bool(honor_cipher_order, false, UserOpts, Opts), - - {Where1, Cookie} = get_opt_bool(cookie, true, UserOpts, Opts), - assert_version_dep(Where1 =:= new, cookie, Versions, ['tlsv1.3']), - - {Where2, ReNeg} = get_opt_bool(client_renegotiation, true, UserOpts, Opts), - assert_version_dep(Where2 =:= new, client_renegotiation, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - - Opts1 = case get_opt(dh, undefined, UserOpts, Opts) of - {Where, DH} when is_binary(DH) -> - warn_override(Where, UserOpts, dh, [dhfile], LogLevel), - Opts#{dh => DH}; - {new, DH} -> - option_error(dh, DH); - {_, undefined} -> - case get_opt_file(dhfile, unbound, UserOpts, Opts) of - {default, unbound} -> Opts; - {_, DHFile} -> Opts#{dhfile => DHFile} - end - end, - - Opts1#{honor_ecc_order => ECC, honor_cipher_order => Cipher, - cookie => Cookie, client_renegotiation => ReNeg}; -opt_server(UserOpts, Opts, #{role := client}) -> - assert_server_only(honor_ecc_order, UserOpts), - assert_server_only(honor_cipher_order, UserOpts), - assert_server_only(cookie, UserOpts), - assert_server_only(client_renegotiation, UserOpts), - assert_server_only(dh, UserOpts), - assert_server_only(dhfile, UserOpts), - Opts. - -opt_client(UserOpts, #{versions := Versions} = Opts, #{role := client}) -> - {Where, FB} = get_opt_bool(fallback, false, UserOpts, Opts), - assert_version_dep(Where =:= new, fallback, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - - {_, CHC} = get_opt_list(customize_hostname_check, [], UserOpts, Opts), - - ValidMFL = [undefined, ?MAX_FRAGMENT_LENGTH_BYTES_1, ?MAX_FRAGMENT_LENGTH_BYTES_2, %% RFC 6066, Section 4 - ?MAX_FRAGMENT_LENGTH_BYTES_3, ?MAX_FRAGMENT_LENGTH_BYTES_4], - {_, MFL} = get_opt_of(max_fragment_length, ValidMFL, undefined, UserOpts, Opts), - - Opts#{fallback => FB, customize_hostname_check => CHC, max_fragment_length => MFL}; -opt_client(UserOpts, Opts, #{role := server}) -> - assert_client_only(fallback, UserOpts), - assert_client_only(customize_hostname_check, UserOpts), - assert_client_only(max_fragment_length, UserOpts), - Opts#{customize_hostname_check => []}. - -opt_renegotiate(UserOpts, #{versions := Versions} = Opts, _Env) -> - {Where1, KUA} = get_opt_pos_int(key_update_at, ?KEY_USAGE_LIMIT_AES_GCM, UserOpts, Opts), - assert_version_dep(Where1 =:= new, key_update_at, Versions, ['tlsv1.3']), - - %% Undocumented, old ? - {_, RA0} = get_opt_pos_int(renegotiate_at, ?DEFAULT_RENEGOTIATE_AT, UserOpts, Opts), - RA = min(RA0, ?DEFAULT_RENEGOTIATE_AT), %% Override users choice without notifying ?? - - {Where3, SR} = get_opt_bool(secure_renegotiate, true, UserOpts, Opts), - assert_version_dep(Where3 =:= new, secure_renegotiate, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - - Opts#{secure_renegotiate => SR, key_update_at => KUA, renegotiate_at => RA}. - -opt_reuse_sessions(UserOpts, #{versions := Versions} = Opts, #{role := client}) -> - {Where1, RUSS} = get_opt_of(reuse_sessions, [true, false, save], true, UserOpts, Opts), - - {Where2, RS} = RST = get_opt(reuse_session, undefined, UserOpts, Opts), - case RST of - {new, Bin} when is_binary(Bin) -> ok; - {new, {B1,B2}} when is_binary(B1), is_binary(B2) -> ok; - {new, Bad} -> option_error(reuse_session, Bad); - {_, _} -> ok - end, - - assert_version_dep(Where1 =:= new, reuse_sessions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - assert_version_dep(Where2 =:= new, reuse_session, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - Opts#{reuse_sessions => RUSS, reuse_session => RS}; -opt_reuse_sessions(UserOpts, #{versions := Versions} = Opts, #{role := server}) -> - {Where1, RUSS} = get_opt_bool(reuse_sessions, true, UserOpts, Opts), - - DefRS = fun(_, _, _, _) -> true end, - {Where2, RS} = get_opt_fun(reuse_session, 4, DefRS, UserOpts, Opts), - - assert_version_dep(Where1 =:= new, reuse_sessions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - assert_version_dep(Where2 =:= new, reuse_session, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - Opts#{reuse_sessions => RUSS, reuse_session => RS}. - -opt_identity(UserOpts, #{versions := Versions} = Opts, _Env) -> - PSK = case get_opt_list(psk_identity, undefined, UserOpts, Opts) of - {new, PSK0} -> - PSK1 = unicode:characters_to_binary(PSK0), - PSKSize = byte_size(PSK1), - assert_version_dep(psk_identity, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - option_error(not (0 < PSKSize andalso PSKSize < 65536), - psk_identity, {psk_identity, PSK0}), - PSK1; - {_, PSK0} -> - PSK0 - end, - - SRP = case get_opt(srp_identity, undefined, UserOpts, Opts) of - {new, {S1, S2}} when is_list(S1), is_list(S2) -> - User = unicode:characters_to_binary(S1), - UserSize = byte_size(User), - assert_version_dep(srp_identity, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - option_error(not (0 < UserSize andalso UserSize < 65536), - srp_identity, {srp_identity, PSK0}), - {User, unicode:characters_to_binary(S2)}; - {new, Err} -> - option_error(srp_identity, Err); - {_, SRP0} -> - SRP0 - end, - - ULF = case get_opt(user_lookup_fun, undefined, UserOpts, Opts) of - {new, {Fun, _} = ULF0} when is_function(Fun, 3) -> - assert_version_dep(user_lookup_fun, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), - ULF0; - {new, ULF0} -> - option_error(user_lookup_fun, ULF0); - {_, ULF0} -> - ULF0 - end, - - Opts#{psk_identity => PSK, srp_identity => SRP, user_lookup_fun => ULF}. - -opt_supported_groups(UserOpts, #{versions := TlsVsns} = Opts, _Env) -> - SG = case get_opt_list(supported_groups, undefined, UserOpts, Opts) of - {default, undefined} -> - handle_supported_groups_option(groups(default)); - {new, SG0} -> - assert_version_dep(supported_groups, TlsVsns, ['tlsv1.3']), - handle_supported_groups_option(SG0); - {old, SG0} -> - SG0 - end, - - CPHS = case get_opt_list(ciphers, [], UserOpts, Opts) of - {old, CPS0} -> CPS0; - {_, CPS0} -> handle_cipher_option(CPS0, TlsVsns) - end, - - ECCS = try assert_version_dep(eccs, TlsVsns, ['tlsv1.2', 'tlsv1.1', 'tlsv1']) of - _ -> - case get_opt_list(eccs, undefined, UserOpts, Opts) of - {old, ECCS0} -> ECCS0; - {default, _} -> handle_eccs_option(tls_v1:ec_curves(default, 'tlsv1.2')); - {new, ECCS0} -> handle_eccs_option(ECCS0) - end - catch - throw:_ -> - [] - end, - Opts#{ciphers => CPHS, eccs => ECCS, supported_groups => SG}. - -opt_crl(UserOpts, Opts, _Env) -> - {_, Check} = get_opt_of(crl_check, [best_effort, peer, true, false], false, UserOpts, Opts), - Cache = case get_opt(crl_cache, {ssl_crl_cache, {internal, []}}, UserOpts, Opts) of - {_, {Cb, {_Handle, Options}} = Value} when is_atom(Cb), is_list(Options) -> - Value; - {_, Err} -> - option_error(crl_cache, Err) - end, - Opts#{crl_check => Check, crl_cache => Cache}. - -opt_handshake(UserOpts, Opts, _Env) -> - {_, HS} = get_opt_of(handshake, [hello, full], full, UserOpts, Opts), - - {_, MHSS} = get_opt_int(max_handshake_size, 1, ?MAX_UNIT24, ?DEFAULT_MAX_HANDSHAKE_SIZE, - UserOpts, Opts), - - Opts#{handshake => HS, max_handshake_size => MHSS}. - -opt_use_srtp(UserOpts, #{protocol := Protocol} = Opts, _Env) -> - UseSRTP = case get_opt_map(use_srtp, undefined, UserOpts, Opts) of - {old, UseSRTP0} -> - UseSRTP0; - {default, undefined} -> - undefined; - {new, UseSRTP1} -> - assert_protocol_dep(use_srtp, Protocol, [dtls]), - validate_use_srtp(UseSRTP1) - end, - case UseSRTP of - #{} -> Opts#{use_srtp => UseSRTP}; - _ -> Opts - end. - -validate_use_srtp(#{protection_profiles := [_|_] = PPs} = UseSRTP) -> - case maps:keys(UseSRTP) -- [protection_profiles, mki] of - [] -> ok; - Extra -> option_error(use_srtp, {unknown_parameters, Extra}) - end, - IsValidProfile = fun(<<_, _>>) -> true; (_) -> false end, - case lists:all(IsValidProfile, PPs) of - true -> ok; - false -> option_error(use_srtp, {invalid_protection_profiles, PPs}) - end, - case UseSRTP of - #{mki := MKI} when not is_binary(MKI) -> - option_error(use_srtp, {invalid_mki, MKI}); - #{mki := _} -> - UseSRTP; - #{} -> - UseSRTP#{mki => <<>>} - end; - -validate_use_srtp(#{} = UseSRTP) -> - option_error(use_srtp, {no_protection_profiles, UseSRTP}). - - -opt_process(UserOpts, Opts0, _Env) -> - Opts1 = set_opt_list(receiver_spawn_opts, [], UserOpts, Opts0), - Opts2 = set_opt_list(sender_spawn_opts, [], UserOpts, Opts1), - %% {_, SSO} = get_opt_list(sender_spawn_opts, [], UserOpts, Opts), - %% Opts = Opts1#{receiver_spawn_opts => RSO, sender_spawn_opts => SSO}, - set_opt_int(hibernate_after, 0, infinity, infinity, UserOpts, Opts2). - -%%%% - -get_opt(Opt, Default, UserOpts, Opts) -> - case maps:get(Opt, UserOpts, unbound) of - unbound -> - case maps:get(maybe_map_key_internal(Opt), Opts, unbound) of - unbound -> %% Uses default value - {default, Default}; - Value -> %% Uses already set value (merge) - {old, Value} - end; - Value -> %% Uses new user option - {new, Value} - end. - -get_opt_of(Opt, Valid, Default, UserOpts, Opts) -> - case get_opt(Opt, Default, UserOpts, Opts) of - {new, Value} = Res -> - case lists:member(Value, Valid) of - true -> Res; - false -> option_error(Opt, Value) - end; - Res -> - Res - end. - -get_opt_bool(Opt, Default, UserOpts, Opts) -> - case get_opt(Opt, Default, UserOpts, Opts) of - {_, Value} = Res when is_boolean(Value) -> Res; - {_, Value} -> option_error(Opt, Value) - end. - -get_opt_pos_int(Opt, Default, UserOpts, Opts) -> - get_opt_int(Opt, 1, infinity, Default, UserOpts, Opts). - -get_opt_int(Opt, Min, Max, Default, UserOpts, Opts) -> - case get_opt(Opt, Default, UserOpts, Opts) of - {_, Value} = Res when is_integer(Value), Min =< Value, Value =< Max -> - Res; - {_, Value} = Res when Value =:= infinity, Max =:= infinity -> - Res; - {_, Value} -> - option_error(Opt, Value) - end. - -get_opt_fun(Opt, Arity, Default, UserOpts, Opts) -> - case get_opt(Opt, Default, UserOpts, Opts) of - {_, Fun} = Res when is_function(Fun, Arity) -> Res; - {new, Err} -> option_error(Opt, Err); - Res -> Res - end. - -get_opt_list(Opt, Default, UserOpts, Opts) -> - case get_opt(Opt, Default, UserOpts, Opts) of - {new, Err} when not is_list(Err) -> option_error(Opt, Err); - Res -> Res - end. - -get_opt_bin(Opt, Default, UserOpts, Opts) -> - case get_opt(Opt, Default, UserOpts, Opts) of - {new, Err} when not is_binary(Err) -> option_error(Opt, Err); - Res -> Res - end. - -get_opt_file(Opt, Default, UserOpts, Opts) -> - case get_opt(Opt, Default, UserOpts, Opts) of - {new, File} -> {new, validate_filename(File, Opt)}; - Res -> Res - end. - -set_opt_bool(Opt, Default, UserOpts, Opts) -> - case maps:get(Opt, UserOpts, Default) of - Default -> Opts; - Value when is_boolean(Value) -> Opts#{Opt => Value}; - Value -> option_error(Opt, Value) - end. - -get_opt_map(Opt, Default, UserOpts, Opts) -> - case get_opt(Opt, Default, UserOpts, Opts) of - {new, Err} when not is_map(Err) -> option_error(Opt, Err); - Res -> Res - end. - -set_opt_int(Opt, Min, Max, Default, UserOpts, Opts) -> - case maps:get(Opt, UserOpts, Default) of - Default -> - Opts; - Value when is_integer(Value), Min =< Value, Value =< Max -> - Opts#{Opt => Value}; - Value when Value =:= infinity, Max =:= infinity -> - Opts#{Opt => Value}; - Value -> - option_error(Opt, Value) - end. - -set_opt_list(Opt, Default, UserOpts, Opts) -> - case maps:get(Opt, UserOpts, []) of - Default -> - Opts; - List when is_list(List) -> - Opts#{Opt => List}; - Value -> - option_error(Opt, Value) - end. - -set_opt_new(new, Opt, Default, Value, Opts) - when Default =/= Value -> - Opts#{Opt => Value}; -set_opt_new(_, _, _, _, Opts) -> - Opts. - -%%%% - -default_cb_info(tls) -> - {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive}; -default_cb_info(dtls) -> - {gen_udp, udp, udp_closed, udp_error, udp_passive}. - -handle_cb_info({V1, V2, V3, V4}) -> - {V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "_passive")}; -handle_cb_info(CbInfo) when tuple_size(CbInfo) =:= 5 -> - CbInfo; -handle_cb_info(CbInfo) -> - option_error(cb_info, CbInfo). - -handle_option_cb_info(Options, Protocol) -> - CbInfo = proplists:get_value(cb_info, Options, default_cb_info(Protocol)), - handle_cb_info(CbInfo). - -maybe_map_key_internal(client_preferred_next_protocols) -> - next_protocol_selector; -maybe_map_key_internal(K) -> - K. - -split_options(Opts0, AllOptions) -> - Opts1 = proplists:expand([{binary, [{mode, binary}]}, - {list, [{mode, list}]}], Opts0), - Opts2 = handle_option_format(Opts1, []), - %% Remove deprecated ssl_imp option - Opts = proplists:delete(ssl_imp, Opts2), - - DeleteUserOpts = fun(Key, PropList) -> proplists:delete(Key, PropList) end, - AllOpts = [cb_info, client_preferred_next_protocols] ++ AllOptions, - SockOpts = lists:foldl(DeleteUserOpts, Opts, AllOpts), - {Opts -- SockOpts, SockOpts}. - -assert_server_only(Option, Opts) -> - Value = maps:get(Option, Opts, undefined), - role_error(Value =/= undefined, server_only, Option). -assert_client_only(Option, Opts) -> - Value = maps:get(Option, Opts, undefined), - role_error(Value =/= undefined, client_only, Option). - -assert_server_only(client, Bool, Option) -> - role_error(Bool, server_only, Option); -assert_server_only(_, _, _) -> - ok. - -role_error(false, _ErrorDesc, _Option) -> - ok; -role_error(true, ErrorDesc, Option) - when ErrorDesc =:= client_only; ErrorDesc =:= server_only -> - throw_error({option, ErrorDesc, Option}). - -option_incompatible(false, _Options) -> ok; -option_incompatible(true, Options) -> option_incompatible(Options). - --spec option_incompatible(_) -> no_return(). -option_incompatible(Options) -> - throw_error({options, incompatible, Options}). - -option_error(false, _, _What) -> true; -option_error(true, Tag, What) -> option_error(Tag,What). - --spec option_error(_,_) -> no_return(). -option_error(Tag, What) -> - throw_error({options, {Tag, What}}). - --spec throw_error(_) -> no_return(). -throw_error(Err) -> - throw({error, Err}). - -assert_protocol_dep(Option, Protocol, AllowedProtos) -> - case lists:member(Protocol, AllowedProtos) of - true -> ok; - false -> option_incompatible([Option, {protocol, Protocol}]) - end. - -assert_version_dep(Option, Vsns, AllowedVsn) -> - assert_version_dep(true, Option, Vsns, AllowedVsn). - -assert_version_dep(false, _, _, _) -> true; -assert_version_dep(true, Option, SSLVsns, AllowedVsn) -> - case is_dtls_configured(SSLVsns) of - true -> %% TODO: Check option dependency for DTLS - true; - false -> - APIVsns = lists:map(fun tls_record:protocol_version/1, SSLVsns), - Set1 = sets:from_list(APIVsns), - Set2 = sets:from_list(AllowedVsn), - case sets:size(sets:intersection(Set1, Set2)) > 0 of - true -> ok; - false -> option_incompatible([Option, {versions, APIVsns}]) - end - end. - -warn_override(new, UserOpts, NewOpt, OldOpts, LogLevel) -> - Check = fun(Key) -> maps:is_key(Key,UserOpts) end, - case lists:filter(Check, OldOpts) of - [] -> ok; - Ignored -> - Desc = lists:flatten(io_lib:format("Options ~w are ignored", [Ignored])), - Reas = lists:flatten(io_lib:format("Option ~w is set", [NewOpt])), - ssl_logger:log(notice, LogLevel, #{description => Desc, reason => Reas}, ?LOCATION) - end; -warn_override(_, _UserOpts, _NewOpt, _OldOpts, _LogLevel) -> - ok. - -is_dtls_configured(Versions) -> - lists:any(fun (Ver) -> ?DTLS_1_X(Ver) end, Versions). - -handle_hashsigns_option(Value, Version) -> - try - if ?TLS_GTE(Version, ?TLS_1_3) -> - tls_v1:signature_schemes(Version, Value); - (Version =:= ?TLS_1_2) -> - tls_v1:signature_algs(Version, Value); - true -> - undefined - end - catch error:function_clause -> - option_error(signature_algs, Value) - end. - -handle_signature_algorithms_option(Value, Version) -> - try tls_v1:signature_schemes(Version, Value) - catch error:function_clause -> - option_error(signature_algs_cert, Value) - end. - -validate_filename(FN, _Option) when is_binary(FN), FN =/= <<>> -> - FN; -validate_filename([_|_] = FN, _Option) -> - Enc = file:native_name_encoding(), - unicode:characters_to_binary(FN, unicode, Enc); -validate_filename(FN, Option) -> - option_error(Option, FN). - -validate_server_cert_opts(_Opts, #{validate_certs_or_anon_ciphers := false}) -> - ok; -validate_server_cert_opts(#{certs_keys := [_|_]=CertsKeys, ciphers := CPHS, versions := Versions}, _) -> - validate_certs_or_anon_ciphers(CertsKeys, CPHS, Versions); -validate_server_cert_opts(#{ciphers := CPHS, versions := Versions}, _) -> - validate_anon_ciphers(CPHS, Versions). - -validate_certs_or_anon_ciphers(CertsKeys, Ciphers, Versions) -> - CheckCertsAndKeys = - fun(Map) -> - (maps:is_key(cert, Map) orelse maps:is_key(certfile, Map)) - andalso (maps:is_key(key, Map) orelse maps:is_key(keyfile, Map)) - end, - case lists:any(CheckCertsAndKeys, CertsKeys) of - true -> ok; - false -> validate_anon_ciphers(Ciphers, Versions) - end. - -validate_anon_ciphers(Ciphers, Versions) -> - MakeSet = fun(Version, Acc) -> - Set = sets:from_list(ssl_cipher:anonymous_suites(Version)), - sets:union(Set, Acc) - end, - Anonymous = lists:foldl(MakeSet, sets:new(), Versions), - CiphersSet = sets:from_list(Ciphers, [{version,2}]), - case sets:is_disjoint(Anonymous, CiphersSet) of - false -> ok; - true -> option_error(certs_keys, cert_and_key_required) - end. - -%% Do not allow configuration of TLS 1.3 with a gap where TLS 1.2 is not supported -%% as that configuration can trigger the built in version downgrade protection -%% mechanism and the handshake can fail with an Illegal Parameter alert. -tls_validate_version_gap(Versions) -> - case lists:member('tlsv1.3', Versions) of - true when length(Versions) >= 2 -> - case lists:member('tlsv1.2', Versions) of - true -> - Versions; - false -> - throw({error, {options, missing_version, {'tlsv1.2', {versions, Versions}}}}) - end; - _ -> - Versions - end. - -emulated_options(undefined, undefined, Protocol, Opts) -> - case Protocol of - tls -> - tls_socket:emulated_options(Opts); - dtls -> - dtls_socket:emulated_options(Opts) - end; -emulated_options(Transport, Socket, Protocol, Opts) -> - EmulatedOptions = tls_socket:emulated_options(), - {ok, Original} = tls_socket:getopts(Transport, Socket, EmulatedOptions), - {Inet, Emulated0} = emulated_options(undefined, undefined, Protocol, Opts), - {Inet, lists:ukeymerge(1, Emulated0, Original)}. - -handle_cipher_option(Value, Versions) when is_list(Value) -> - try binary_cipher_suites(Versions, Value) of - Suites -> - Suites - catch - exit:_ -> - option_error(ciphers, Value); - error:_-> - option_error(ciphers, Value) - end. - -binary_cipher_suites([?TLS_1_3], []) -> - %% Defaults to all supported suites that does - %% not require explicit configuration TLS-1.3 - %% only mode. - default_binary_suites(exclusive, ?TLS_1_3); -binary_cipher_suites([Version| _], []) -> - %% Defaults to all supported suites that does - %% not require explicit configuration - default_binary_suites(default, Version); -binary_cipher_suites(Versions, [Map|_] = Ciphers0) when is_map(Map) -> - Ciphers = [ssl_cipher_format:suite_map_to_bin(C) || C <- Ciphers0], - binary_cipher_suites(Versions, Ciphers); -binary_cipher_suites(Versions, [Tuple|_] = Ciphers0) when is_tuple(Tuple) -> - Ciphers = [ssl_cipher_format:suite_map_to_bin(tuple_to_map(C)) || C <- Ciphers0], - binary_cipher_suites(Versions, Ciphers); -binary_cipher_suites(Versions, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> - All = all_suites(Versions), - case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, All)] of - [] -> - %% Defaults to all supported suites that does - %% not require explicit configuration - binary_cipher_suites(Versions, []); - Ciphers -> - Ciphers - end; -binary_cipher_suites(Versions, [Head | _] = Ciphers0) when is_list(Head) -> - %% Format: ["RC4-SHA","RC4-MD5"] - Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- Ciphers0], - binary_cipher_suites(Versions, Ciphers); -binary_cipher_suites(Versions, Ciphers0) -> - %% Format: "RC4-SHA:RC4-MD5" - Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- string:lexemes(Ciphers0, ":")], - binary_cipher_suites(Versions, Ciphers). - -default_binary_suites(exclusive, Version) -> - ssl_cipher:filter_suites(tls_v1:exclusive_suites(Version)); -default_binary_suites(default, Version) -> - ssl_cipher:filter_suites(ssl_cipher:suites(Version)). - -all_suites([?TLS_1_3]) -> - tls_v1:exclusive_suites(?TLS_1_3); -all_suites([?TLS_1_3, Version1 |_]) -> - all_suites([?TLS_1_3]) ++ - ssl_cipher:all_suites(Version1) ++ - ssl_cipher:anonymous_suites(Version1); -all_suites([Version|_]) -> - ssl_cipher:all_suites(Version) ++ - ssl_cipher:anonymous_suites(Version). - -tuple_to_map({Kex, Cipher, Mac}) -> - #{key_exchange => Kex, - cipher => Cipher, - mac => Mac, - prf => default_prf}; -tuple_to_map({Kex, Cipher, Mac, Prf}) -> - #{key_exchange => Kex, - cipher => Cipher, - mac => tuple_to_map_mac(Cipher, Mac), - prf => Prf}. - -%% Backwards compatible -tuple_to_map_mac(aes_128_gcm, _) -> - aead; -tuple_to_map_mac(aes_256_gcm, _) -> - aead; -tuple_to_map_mac(chacha20_poly1305, _) -> - aead; -tuple_to_map_mac(_, MAC) -> - MAC. - -handle_eccs_option(Value) when is_list(Value) -> - try tls_v1:ecc_curves(Value) of - Curves -> - option_error(Curves =:= [], eccs, none_valid), - #elliptic_curves{elliptic_curve_list = Curves} - catch - exit:_ -> option_error(eccs, Value); - error:_ -> option_error(eccs, Value) - end. - -handle_supported_groups_option(Value) when is_list(Value) -> - try tls_v1:groups(Value) of - Groups -> - option_error(Groups =:= [], supported_groups, none_valid), - #supported_groups{supported_groups = Groups} - catch - exit:_ -> option_error(supported_groups, Value); - error:_ -> option_error(supported_groups, Value) - end. - - -spec do_format_error( string() | closed | {tls_alert, {_, Description :: string()}} @@ -5168,38 +3801,6 @@ file_desc(keyfile) -> file_desc(dhfile) -> "Invalid DH params file ". -make_next_protocol_selector(undefined) -> - undefined; -make_next_protocol_selector({Precedence, PrefProtcol} = V) -> - option_error(not is_list(PrefProtcol), client_preferred_next_protocols, V), - make_next_protocol_selector({Precedence, PrefProtcol, ?NO_PROTOCOL}); -make_next_protocol_selector({Precedence, AllProtocols, DefP} = V) -> - option_error(not is_list(AllProtocols), client_preferred_next_protocols, V), - option_error(not (is_binary(DefP) andalso byte_size(DefP) < 256), client_preferred_next_protocols, V), - validate_protocols(true, client_preferred_next_protocols, AllProtocols), - case Precedence of - client -> - fun(Advertised) -> - Search = fun(P) -> lists:member(P, Advertised) end, - case lists:search(Search, AllProtocols) of - false -> DefP; - {value, Preferred} -> Preferred - end - end; - server -> - fun(Advertised) -> - Search = fun(P) -> lists:member(P, AllProtocols) end, - case lists:search(Search, Advertised) of - false -> DefP; - {value, Preferred} -> Preferred - end - end; - Value -> - option_error(client_preferred_next_protocols, {invalid_precedence, Value}) - end; -make_next_protocol_selector(What) -> - option_error(client_preferred_next_protocols, What). - connection_cb(tls) -> tls_gen_connection; connection_cb(dtls) -> @@ -5207,36 +3808,6 @@ connection_cb(dtls) -> connection_cb(Opts) -> connection_cb(proplists:get_value(protocol, Opts, tls)). - -%% Assert that basic options are on the format {Key, Value} -%% with a few exceptions and phase out log_alert -handle_option_format([], Acc) -> - lists:reverse(Acc); -handle_option_format([{log_alert, Bool} | Rest], Acc) when is_boolean(Bool) -> - case proplists:get_value(log_level, Acc ++ Rest, undefined) of - undefined -> - handle_option_format(Rest, [{log_level, - map_log_level(Bool)} | Acc]); - _ -> - handle_option_format(Rest, Acc) - end; -handle_option_format([{Key,_} = Opt | Rest], Acc) when is_atom(Key) -> - handle_option_format(Rest, [Opt | Acc]); -%% Handle exceptions -handle_option_format([{raw,_,_,_} = Opt | Rest], Acc) -> - handle_option_format(Rest, [Opt | Acc]); -handle_option_format([inet = Opt | Rest], Acc) -> - handle_option_format(Rest, [Opt | Acc]); -handle_option_format([inet6 = Opt | Rest], Acc) -> - handle_option_format(Rest, [Opt | Acc]); -handle_option_format([Value | _], _) -> - option_error(option_not_a_key_value_tuple, Value). - -map_log_level(true) -> - notice; -map_log_level(false) -> - none. - include_security_info([]) -> false; include_security_info([Item | Items]) -> @@ -5247,28 +3818,11 @@ include_security_info([Item | Items]) -> include_security_info(Items) end. - add_filter(undefined, Filters) -> Filters; add_filter(Filter, Filters) -> [Filter | Filters]. -unambiguous_path(Value) -> - AbsName = filename:absname(Value), - UP = case file:read_link(AbsName) of - {ok, PathWithNoLink} -> - case filename:pathtype(PathWithNoLink) of - relative -> - Dirname = filename:dirname(AbsName), - filename:join([Dirname, PathWithNoLink]); - _ -> - PathWithNoLink - end; - _ -> - AbsName - end, - validate_filename(UP, cacertfile). - -compile({inline, encode_packet/2}). encode_packet(Packet, Data) -> Len = iolist_size(Data), @@ -5283,15 +3837,10 @@ encode_packet(Packet, Data) -> Data end. -%%%################################################################ -%%%# -%%%# Tracing -%%%# +%%%-------------------------------------------------------------- +%%% Tracing +%%%-------------------------------------------------------------------- -doc false. -handle_trace(csp, {call, {?MODULE, opt_stapling, [UserOpts | _]}}, Stack) -> - {format_ocsp_params(UserOpts), Stack}; -handle_trace(csp, {return_from, {?MODULE, opt_stapling, 3}, Return}, Stack) -> - {format_ocsp_params(Return), Stack}; handle_trace(rle, {call, {?MODULE, listen, Args}}, Stack0) -> Role = server, {io_lib:format("(*~w) Args = ~W", [Role, Args, 10]), [{role, Role} | Stack0]}; @@ -5299,8 +3848,3 @@ handle_trace(rle, {call, {?MODULE, connect, Args}}, Stack0) -> Role = client, {io_lib:format("(*~w) Args = ~W", [Role, Args, 10]), [{role, Role} | Stack0]}. -format_ocsp_params(Map) -> - Stapling = maps:get(stapling, Map, '?'), - Nonce = maps:get(ocsp_nonce, Map, '?'), - io_lib:format("Stapling = ~W Nonce = ~W", [Stapling, 5, Nonce, 5]). - diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl index 88c6bdcbb2c..338861eae9d 100644 --- a/lib/ssl/src/ssl_config.erl +++ b/lib/ssl/src/ssl_config.erl @@ -28,17 +28,29 @@ -include_lib("public_key/include/public_key.hrl"). -define(DEFAULT_MAX_SESSION_CACHE, 1000). +-define(TWO_HOURS, 7200). +-define(SEVEN_DAYS, 604800). +%% Connection parameter configuration -export([init/2, - pre_1_3_session_opts/1, + new_emulated/2, + handle_options/3, + handle_options/4, + update_options/3 + ]). + +%% Application configuration +-export([pre_1_3_session_opts/1, get_max_early_data_size/0, get_ticket_lifetime/0, get_ticket_store_size/0, get_internal_active_n/0, - get_internal_active_n/1, - new_emulated/2 + get_internal_active_n/1 ]). +%% Tracing +-export([handle_trace/3]). + %%==================================================================== %% Internal application API %%==================================================================== @@ -49,14 +61,99 @@ init(SslOpts, Role) -> CertKeyAlts = init_certs_keys(SslOpts, Role, PemCache), {ok, Config#{cert_key_alts => CertKeyAlts, dh_params => DHParams}}. +new_emulated([], EmOpts) -> + EmOpts; +new_emulated(NewEmOpts, _) -> + NewEmOpts. + +handle_options(Opts, Role, Host) -> + handle_options(undefined, undefined, Opts, Role, Host). + +handle_options(Socket, Opts, Role, Host) -> + CbInfo = handle_option_cb_info(Opts, tls), + Transport = element(1, CbInfo), + handle_options(Transport, Socket, Opts, Role, Host). + +%% Handle all options in listen, connect and handshake +handle_options(Transport, Socket, Opts0, Role, Host) -> + {UserSslOptsList, SockOpts0} = split_options(Opts0, ssl_options()), + NeedValidate = not (Socket == undefined) andalso Role =:= server, %% handshake options + Env = #{role => Role, host => Host, + validate_certs_or_anon_ciphers => NeedValidate + }, + SslOpts = process_options(UserSslOptsList, #{}, Env), + + %% Handle special options + #{protocol := Protocol} = SslOpts, + {Sock, Emulated} = emulated_options(Transport, Socket, Protocol, SockOpts0), + 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 + }}. + + +pre_1_3_session_opts(Role) -> + {Cb, InitArgs} = session_cb_opts(Role), + CbOpts = #{session_cb => Cb, + session_cb_init_args => InitArgs}, + LifeTime = session_lifetime(Role), + Max = max_session_cache_size(Role), + CbOpts#{lifetime => LifeTime, max => Max}. + +get_ticket_lifetime() -> + case application_int(server_session_ticket_lifetime, ?TWO_HOURS) of + Seconds when Seconds =< ?SEVEN_DAYS -> + Seconds; + _ -> + ?TWO_HOURS + end. + +get_ticket_store_size() -> + application_int(server_session_ticket_store_size, 1000). + +get_max_early_data_size() -> + application_int(server_session_ticket_max_early_data, ?DEFAULT_MAX_EARLY_DATA_SIZE). + +get_internal_active_n() -> + get_internal_active_n(false). + +get_internal_active_n(true) -> + %% Start with a random number between 1 and ?INTERNAL_ACTIVE_N + %% In most cases distribution connections are established all at + %% the same time, and flow control engages with ?INTERNAL_ACTIVE_N for + %% all connections. Which creates a wave of "passive" messages, leading + %% to significant bump of memory & scheduler utilisation. Starting with + %% a random number between 1 and ?INTERNAL_ACTIVE_N helps to spread the + %% spike. + erlang:system_time() rem ?INTERNAL_ACTIVE_N + 1; +get_internal_active_n(false) -> + application_int(internal_active_n, ?INTERNAL_ACTIVE_N). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +%%==================================================================== +%% Certificate and Key configuration +%%==================================================================== init_certs_keys(#{certs_keys := CertsKeys} = Opts, Role, PemCache) -> - Pairs = lists:map(fun(CertKey) -> init_cert_key_pair(CertKey, Role, PemCache) end, CertsKeys), + Pairs = lists:map(fun(CertKey) -> + init_cert_key_pair(CertKey, Role, PemCache) + end, CertsKeys), CertKeyGroups = group_pairs(Pairs), prioritize_groups(CertKeyGroups, Opts). init_cert_key_pair(CertKey, Role, PemCache) -> Certs = init_certificates(CertKey, PemCache, Role), - PrivateKey = init_private_key(maps:get(key, CertKey, undefined), CertKey, PemCache), + PrivateKey = init_private_key(maps:get(key, CertKey, undefined), + CertKey, PemCache), #{private_key => PrivateKey, certs => Certs}. group_pairs([#{certs := []}]) -> @@ -74,13 +171,16 @@ group_pairs(Pairs) -> dsa => [] }). -group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed25519'}}} = Pair | Rest], #{eddsa := EDDSA} = Group) -> +group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed25519'}}} = Pair | Rest], + #{eddsa := EDDSA} = Group) -> group_pairs(Rest, Group#{eddsa => [Pair | EDDSA]}); -group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed448'}}} = Pair | Rest], #{eddsa := EDDSA} = Group) -> +group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed448'}}} = Pair | Rest], + #{eddsa := EDDSA} = Group) -> group_pairs(Rest, Group#{eddsa => [Pair | EDDSA]}); group_pairs([#{private_key := #'ECPrivateKey'{}} = Pair | Rest], #{ecdsa := ECDSA} = Group) -> group_pairs(Rest, Group#{ecdsa => [Pair | ECDSA]}); -group_pairs([#{private_key := {#'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}} = Pair | Rest], #{rsa_pss_pss := RSAPSS} = Group) -> +group_pairs([#{private_key := {#'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}} = Pair | Rest], + #{rsa_pss_pss := RSAPSS} = Group) -> group_pairs(Rest, Group#{rsa_pss_pss => [Pair | RSAPSS]}); group_pairs([#{private_key := #'RSAPrivateKey'{}} = Pair | Rest], #{rsa := RSA} = Group) -> group_pairs(Rest, Group#{rsa => [Pair | RSA]}); @@ -98,7 +198,6 @@ group_pairs([#{private_key := #{algorithm := Alg, engine := _}} = Pair | Rest], group_pairs([], Group) -> Group. - prioritize_groups(#{eddsa := EDDSA, ecdsa := ECDSA, rsa_pss_pss := RSAPSS, @@ -177,68 +276,6 @@ prio_dsa(DSA) -> end, lists:sort(Order, DSA). - -pre_1_3_session_opts(Role) -> - {Cb, InitArgs} = session_cb_opts(Role), - CbOpts = #{session_cb => Cb, - session_cb_init_args => InitArgs}, - LifeTime = session_lifetime(Role), - Max = max_session_cache_size(Role), - CbOpts#{lifetime => LifeTime, max => Max}. - -get_ticket_lifetime() -> - case application:get_env(ssl, server_session_ticket_lifetime) of - {ok, Seconds} when is_integer(Seconds) andalso - Seconds =< 604800 -> %% MUST be less than 7 days - Seconds; - _ -> - 7200 %% Default 2 hours - end. - -get_ticket_store_size() -> - case application:get_env(ssl, server_session_ticket_store_size) of - {ok, Size} when is_integer(Size) -> - Size; - _ -> - 1000 - end. - -get_max_early_data_size() -> - case application:get_env(ssl, server_session_ticket_max_early_data) of - {ok, Size} when is_integer(Size) -> - Size; - _ -> - ?DEFAULT_MAX_EARLY_DATA_SIZE - end. - -get_internal_active_n() -> - get_internal_active_n(false). - -get_internal_active_n(true) -> - %% Start with a random number between 1 and ?INTERNAL_ACTIVE_N - %% In most cases distribution connections are established all at - %% the same time, and flow control engages with ?INTERNAL_ACTIVE_N for - %% all connections. Which creates a wave of "passive" messages, leading - %% to significant bump of memory & scheduler utilisation. Starting with - %% a random number between 1 and ?INTERNAL_ACTIVE_N helps to spread the - %% spike. - erlang:system_time() rem ?INTERNAL_ACTIVE_N + 1; -get_internal_active_n(false) -> - case application:get_env(ssl, internal_active_n) of - {ok, N} when is_integer(N) -> - N; - _ -> - ?INTERNAL_ACTIVE_N - end. - -new_emulated([], EmOpts) -> - EmOpts; -new_emulated(NewEmOpts, _) -> - NewEmOpts. - -%%==================================================================== -%% Internal functions -%%==================================================================== init_manager_name(false) -> put(ssl_manager, ssl_manager:name(normal)), put(ssl_pem_cache, ssl_pem_cache:name(normal)); @@ -333,6 +370,18 @@ private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = private_key(Key) -> Key. +ecdsa_support(#{versions := [?TLS_1_3]}) -> + [secp521r1, + secp384r1, + secp256r1, + brainpoolP512r1, + brainpoolP384r1, + brainpoolP256r1 + ]; +ecdsa_support(_) -> + ssl:eccs() -- [x25519, x448]. + + -spec(file_error(_,_) -> no_return()). file_error(File, Throw) -> case Throw of @@ -373,76 +422,1520 @@ dh_file(DbHandle, DHParamFile) -> file_error(DHParamFile, {dhfile, Reason}) end. -session_cb_init_args(client) -> - case application:get_env(ssl, client_session_cb_init_args) of - undefined -> - case application:get_env(ssl, session_cb_init_args) of - {ok, Args} when is_list(Args) -> - Args; - _ -> - [] +%%==================================================================== +%% Connection options +%%==================================================================== +ssl_options() -> + [ + alpn_advertised_protocols, alpn_preferred_protocols, + anti_replay, + beast_mitigation, + cacertfile, cacerts, + cert, certs_keys,certfile, + certificate_authorities, + ciphers, + client_renegotiation, + cookie, + crl_cache, crl_check, + customize_hostname_check, + depth, + dh, dhfile, + + early_data, + eccs, + erl_dist, + fail_if_no_peer_cert, + fallback, + handshake, + hibernate_after, + honor_cipher_order, honor_ecc_order, + keep_secrets, + key, keyfile, + key_update_at, + ktls, + + log_level, + max_handshake_size, + middlebox_comp_mode, + max_fragment_length, + next_protocol_selector, next_protocols_advertised, + stapling, + padding_check, + partial_chain, + password, + protocol, + psk_identity, + receiver_spawn_opts, + renegotiate_at, + reuse_session, reuse_sessions, + + secure_renegotiate, + sender_spawn_opts, + server_name_indication, + session_tickets, + stateless_tickets_seed, + signature_algs, signature_algs_cert, + sni_fun, + sni_hosts, + srp_identity, + supported_groups, + use_ticket, + use_srtp, + user_lookup_fun, + verify, verify_fun, cert_policy_opts, + allow_any_ca_purpose, + versions + ]. + +%% Handle ssl options at handshake, handshake_continue +-doc false. +-spec update_options([any()], client | server, map()) -> map(). +update_options(Opts, Role, InheritedSslOpts) when is_map(InheritedSslOpts) -> + {UserSslOpts, _} = split_options(Opts, ssl_options()), + Env = #{role => Role, validate_certs_or_anon_ciphers => Role == server}, + process_options(UserSslOpts, InheritedSslOpts, Env). + +process_options(UserSslOpts, SslOpts0, Env) -> + %% Reverse option list so we get the last set option if set twice, + %% users depend on it. + UserSslOptsMap = proplists:to_map(lists:reverse(UserSslOpts)), + SslOpts1 = opt_protocol_versions(UserSslOptsMap, SslOpts0, Env), + SslOpts2 = opt_verification(UserSslOptsMap, SslOpts1, Env), + SslOpts3 = opt_certs(UserSslOptsMap, SslOpts2, Env), + SslOpts4 = opt_tickets(UserSslOptsMap, SslOpts3, Env), + SslOpts5 = opt_stapling(UserSslOptsMap, SslOpts4, Env), + SslOpts6 = opt_sni(UserSslOptsMap, SslOpts5, Env), + SslOpts7 = opt_signature_algs(UserSslOptsMap, SslOpts6, Env), + SslOpts8 = opt_alpn(UserSslOptsMap, SslOpts7, Env), + SslOpts9 = opt_mitigation(UserSslOptsMap, SslOpts8, Env), + SslOpts10 = opt_server(UserSslOptsMap, SslOpts9, Env), + SslOpts11 = opt_client(UserSslOptsMap, SslOpts10, Env), + SslOpts12 = opt_renegotiate(UserSslOptsMap, SslOpts11, Env), + SslOpts13 = opt_reuse_sessions(UserSslOptsMap, SslOpts12, Env), + SslOpts14 = opt_identity(UserSslOptsMap, SslOpts13, Env), + SslOpts15 = opt_supported_groups(UserSslOptsMap, SslOpts14, Env), + SslOpts16 = opt_crl(UserSslOptsMap, SslOpts15, Env), + SslOpts17 = opt_handshake(UserSslOptsMap, SslOpts16, Env), + SslOpts18 = opt_use_srtp(UserSslOptsMap, SslOpts17, Env), + SslOpts = opt_process(UserSslOptsMap, SslOpts18, Env), + validate_server_cert_opts(SslOpts, Env), + SslOpts. + +opt_protocol_versions(UserOpts, Opts, Env) -> + {_, PRC} = get_opt_of(protocol, [tls, dtls], tls, UserOpts, Opts), + + LogLevels = [none, all, emergency, alert, critical, error, + warning, notice, info, debug], + + DefaultLevel = case logger:get_module_level(?MODULE) of + [] -> notice; + [{ssl,Level}] -> Level + end, + + {_, LL} = get_opt_of(log_level, LogLevels, DefaultLevel, UserOpts, Opts), + + Opts1 = set_opt_bool(keep_secrets, false, UserOpts, Opts), + + {DistW, Dist} = get_opt_bool(erl_dist, false, UserOpts, Opts1), + option_incompatible(PRC =:= dtls andalso Dist, [{protocol, PRC}, {erl_dist, Dist}]), + Opts2 = set_opt_new(DistW, erl_dist, false, Dist, Opts1), + + {KtlsW, Ktls} = get_opt_bool(ktls, false, UserOpts, Opts1), + option_incompatible(PRC =:= dtls andalso Ktls, [{protocol, PRC}, {ktls, Ktls}]), + Opts3 = set_opt_new(KtlsW, ktls, false, Ktls, Opts2), + + opt_versions(UserOpts, Opts3#{protocol => PRC, log_level => LL}, Env). + +opt_versions(UserOpts, #{protocol := Protocol} = Opts, _Env) -> + Versions = case get_opt(versions, unbound, UserOpts, Opts) of + {default, unbound} -> default_versions(Protocol); + {new, Vs} -> validate_versions(Protocol, Vs); + {old, Vs} -> Vs + end, + + {Where, MCM} = get_opt_bool(middlebox_comp_mode, true, UserOpts, Opts), + assert_version_dep(Where =:= new, middlebox_comp_mode, Versions, ['tlsv1.3']), + Opts1 = set_opt_new(Where, middlebox_comp_mode, true, MCM, Opts), + Opts1#{versions => Versions}. + +default_versions(tls) -> + Vsns0 = tls_record:supported_protocol_versions(), + lists:sort(fun tls_record:is_higher/2, Vsns0); +default_versions(dtls) -> + Vsns0 = dtls_record:supported_protocol_versions(), + lists:sort(fun dtls_record:is_higher/2, Vsns0). + +validate_versions(tls, Vsns0) -> + Validate = + fun(Version) -> + try tls_record:sufficient_crypto_support(Version) of + true -> tls_record:protocol_version_name(Version); + false -> option_error(insufficient_crypto_support, + {Version, {versions, Vsns0}}) + catch error:function_clause -> + option_error(Version, {versions, Vsns0}) + end + end, + Vsns = [Validate(V) || V <- Vsns0], + tls_validate_version_gap(Vsns0), + option_error([] =:= Vsns, versions, Vsns0), + lists:sort(fun tls_record:is_higher/2, Vsns); +validate_versions(dtls, Vsns0) -> + Validate = + fun(Version) -> + try tls_record:sufficient_crypto_support( + dtls_v1:corresponding_tls_version( + dtls_record:protocol_version_name(Version))) of + true -> dtls_record:protocol_version_name(Version); + false-> option_error(insufficient_crypto_support, + {Version, {versions, Vsns0}}) + catch error:function_clause -> + option_error(Version, {versions, Vsns0}) + end + end, + Vsns = [Validate(V) || V <- Vsns0], + option_error([] =:= Vsns, versions, Vsns0), + lists:sort(fun dtls_record:is_higher/2, Vsns). + +opt_verification(UserOpts, Opts0, #{role := Role} = Env) -> + {Verify, Opts1} = + case get_opt_of(verify, [verify_none, verify_peer], default_verify(Role), UserOpts, Opts0) of + {old, Val} -> + {Val, Opts0}; + {_, verify_none} -> + {verify_none, Opts0#{verify => verify_none, verify_fun => {none_verify_fun(), []}}}; + {_, verify_peer} -> + %% If 'verify' is changed from verify_none to verify_peer, (via update_options/3) + %% the 'verify_fun' must also be changed to undefined. + %% i.e remove verify_none fun + Temp = Opts0#{verify => verify_peer, verify_fun => undefined}, + {verify_peer, maps:remove(fail_if_no_peer_cert, Temp)} + end, + Opts2 = opt_cacerts(UserOpts, Opts1, Env), + {_, PartialChain} = get_opt_fun(partial_chain, 1, fun(_) -> unknown_ca end, UserOpts, Opts2), + + DefFailNoPeer = Role =:= server andalso Verify =:= verify_peer, + {_, FailNoPeerCert} = get_opt_bool(fail_if_no_peer_cert, DefFailNoPeer, UserOpts, Opts2), + assert_server_only(Role, FailNoPeerCert, fail_if_no_peer_cert), + option_incompatible(FailNoPeerCert andalso Verify =:= verify_none, + [{verify, verify_none}, {fail_if_no_peer_cert, true}]), + + Opts3 = set_opt_int(depth, 0, 255, ?DEFAULT_DEPTH, UserOpts, Opts2), + + Opts4 = case Role of + client -> + opt_verify_fun(UserOpts, Opts3#{partial_chain => PartialChain}, + Env); + server -> + opt_verify_fun(UserOpts, Opts3#{partial_chain => PartialChain, + fail_if_no_peer_cert => FailNoPeerCert}, + Env) + end, + Opts = opt_policies(UserOpts, Opts4), + opt_extend_keyusage(UserOpts, Opts). + +default_verify(client) -> + %% Server authenication is by default requiered + verify_peer; +default_verify(server) -> + %% Client certification is an optional part of the protocol + verify_none. + +opt_verify_fun(UserOpts, Opts, _Env) -> + %%DefVerifyNoneFun = {default_verify_fun(), []}, + VerifyFun = case get_opt(verify_fun, undefined, UserOpts, Opts) of + {_, {F,_} = FA} when is_function(F, 3); is_function(F, 4) -> + FA; + {_, UserFun} when is_function(UserFun, 1) -> + {convert_verify_fun(), UserFun}; + {_, undefined} -> + undefined; + {_, Value} -> + option_error(verify_fun, Value) + end, + Opts#{verify_fun => VerifyFun}. + +none_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. + +convert_verify_fun() -> + fun(_,{bad_cert, _} = Reason, OldFun) -> + case OldFun([Reason]) of + true -> {valid, OldFun}; + false -> {fail, Reason} end; - {ok, Args} -> - Args + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end. + +opt_policies(UserOpts, Opts) -> + case get_opt(cert_policy_opts, [], UserOpts, Opts) of + {default, []} -> + Opts#{cert_policy_opts => []}; + {old, POpts} -> + Opts#{cert_policy_opts => POpts}; + {_, POpts} -> + validate_policy_opts(POpts), + Opts#{cert_policy_opts => POpts} + end. + +opt_extend_keyusage(UserOpts, Opts) -> + case get_opt_bool(allow_any_ca_purpose, false, UserOpts, Opts) of + {default, Value} -> + Opts#{allow_any_ca_purpose => Value}; + {old, _OldValue} -> + Opts; + {new, NewValue} -> + Opts#{allow_any_ca_purpose => NewValue} + end. + +validate_policy_opts([]) -> + true; +validate_policy_opts([{policy_set, OidList} | Rest]) when is_list(OidList) -> + validate_policy_opts(Rest); +validate_policy_opts([{Opt, Bool} | Rest]) when Opt == explicit_policy; + Opt == inhibit_policy_mapping; + Opt == inhibit_any_policy -> + case is_boolean(Bool) of + true -> + validate_policy_opts(Rest); + false -> + option_error(cert_policy_opts, {Opt, Bool}) end; -session_cb_init_args(server) -> - case application:get_env(ssl, server_session_cb_init_args) of +validate_policy_opts([Opt| _]) -> + option_error(cert_policy_opts, Opt). + +opt_certs(UserOpts, #{log_level := LogLevel, versions := Versions} = Opts0, Env) -> + case get_opt_list(certs_keys, [], UserOpts, Opts0) of + {Where, []} when Where =/= new -> + opt_old_certs(UserOpts, #{}, Opts0, Env); + {old, [CertKey]} -> + opt_old_certs(UserOpts, CertKey, Opts0, Env); + {Where, CKs0} when is_list(CKs0) -> + warn_override(Where, UserOpts, certs_keys, [cert,certfile,key,keyfile,password], LogLevel), + CKs = lists:foldl(fun(CK0, Acc) -> + CK = check_legacy_cert_key(Versions, CK0, #{}, LogLevel), + case maps:size(CK) =:= 0 of + true -> + Acc; + false -> + [CK|Acc] + end + end, [], CKs0), + Opts0#{certs_keys => lists:reverse(CKs)} + end. + +opt_old_certs(UserOpts, CertKeys, #{log_level := LogLevel, versions := Versions}=SSLOpts, _Env) -> + CK = check_legacy_cert_key(Versions, UserOpts, CertKeys, LogLevel), + case maps:size(CK) =:= 0 of + true -> + SSLOpts#{certs_keys => []}; + false -> + SSLOpts#{certs_keys => [CK]} + end. + +check_legacy_cert_key(Versions, UserOpts, CertKeys0, LogLevel) -> + CertKeys1 = handle_leycy_cert_opt(UserOpts, CertKeys0, LogLevel), + CertKeys = handle_legacy_key_opt(Versions, UserOpts, CertKeys1), + handle_possible_legacy_key_password(UserOpts, CertKeys). + +handle_leycy_cert_opt(UserOpts, CertKeys, LogLevel) -> + case get_opt(cert, undefined, UserOpts, CertKeys) of + {Where, Cert} when is_binary(Cert) -> + warn_override(Where, UserOpts, cert, [certfile], LogLevel), + CertKeys#{cert => [Cert]}; + {Where, [C0|_] = Certs} when is_binary(C0) -> + warn_override(Where, UserOpts, cert, [certfile], LogLevel), + CertKeys#{cert => Certs}; + {new, Err0} -> + option_error(cert, Err0); + {_, undefined} -> + case get_opt_file(certfile, unbound, UserOpts, CertKeys) of + {default, unbound} -> CertKeys; + {_, CertFile} -> CertKeys#{certfile => CertFile} + end + end. + +handle_legacy_key_opt(Versions, UserOpts, CertKeys0) -> + case get_opt(key, undefined, UserOpts, CertKeys0) of + {_, undefined} -> + case get_opt_file(keyfile, <<>>, UserOpts, CertKeys0) of + {new, KeyFile} -> + CertKeys0#{keyfile => KeyFile}; + {_, <<>>} -> + case maps:get(certfile, CertKeys0, unbound) of + unbound -> CertKeys0; + KeyFile -> CertKeys0#{keyfile => KeyFile} + end; + {old, _} -> + CertKeys0 + end; + {_, {KF, K0} = Key} + when is_binary(K0), KF =:= rsa; KF =:= dsa; + KF == 'RSAPrivateKey'; KF == 'DSAPrivateKey'; + KF == 'ECPrivateKey'; KF == 'PrivateKeyInfo' -> + CertKeys0#{key => Key}; + {_, #{engine := _, key_id := _, algorithm := Algo} = Key} -> + check_key_algo_version_dep(Versions, Algo), + CertKeys0#{key => Key}; + {_, #{sign_fun := _, algorithm := Algo} = Key} -> + check_key_algo_version_dep(Versions, Algo), + check_key_legacy_version_dep(Versions, Key, Algo), + CertKeys0#{key => Key}; + {_, #{encrypt_fun := _, algorithm := rsa} = Key} -> + check_key_legacy_version_dep(Versions, Key), + CertKeys0#{key => Key}; + {new, Err1} -> + option_error(key, Err1) + end. + +handle_possible_legacy_key_password(UserOpts, CertKeys0) -> + case get_opt(password, unbound, UserOpts, CertKeys0) of + {default, _} -> CertKeys0; + {_, Pwd} when is_binary(Pwd); is_list(Pwd) -> + CertKeys0#{password => fun() -> Pwd end}; + {_, Pwd} when is_function(Pwd, 0) -> + CertKeys0#{password => Pwd}; + {_, Err2} -> + option_error(password, Err2) + end. + +check_key_algo_version_dep(Versions, eddsa) -> + assert_version_dep(key, Versions, ['tlsv1.3']); +check_key_algo_version_dep(Versions, rsa_pss_pss) -> + assert_version_dep(key, Versions, ['tlsv1.3', 'tlsv1.2']); +check_key_algo_version_dep(Versions, dsa) -> + assert_version_dep(key, Versions, ['tlsv1.2', 'tlsv1.1', 'tlsv1']); +check_key_algo_version_dep(_,_) -> + true. + +check_key_legacy_version_dep(Versions, Key, rsa) -> + check_key_legacy_version_dep(Versions, Key); +check_key_legacy_version_dep(_,_,_) -> + true. + +check_key_legacy_version_dep(Versions, Key) -> + EncryptFun = maps:get(encrypt_fun, Key, undefined), + case EncryptFun of undefined -> - case application:get_env(ssl, session_cb_init_args) of - {ok, Args} when is_list(Args) -> - Args; - _ -> + assert_version_dep(key, Versions, ['tlsv1.3', 'tlsv1.2']); + _ -> + assert_version_dep(key, Versions, ['tlsv1.1', 'tlsv1']) + end. + +opt_cacerts(UserOpts, #{verify := Verify, log_level := LogLevel, versions := Versions} = Opts, + #{role := Role}) -> + {_, CaCerts} = get_opt_list(cacerts, undefined, UserOpts, Opts), + + CaCertFile = case get_opt_file(cacertfile, <<>>, UserOpts, Opts) of + {Where1, _FileName} when CaCerts =/= undefined -> + warn_override(Where1, UserOpts, cacerts, [cacertfile], LogLevel), + <<>>; + {new, FileName} -> unambiguous_path(FileName); + {_, FileName} -> FileName + end, + option_incompatible(CaCertFile =:= <<>> andalso CaCerts =:= undefined andalso Verify =:= verify_peer, + [{verify, verify_peer}, {cacerts, undefined}]), + + {Where2, CA} = get_opt_bool(certificate_authorities, Role =:= server, UserOpts, Opts), + case Role of + server -> + assert_version_dep(Where2 =:= new, certificate_authorities, + Versions, ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', 'tlsv1']); + client -> + assert_version_dep(Where2 =:= new, certificate_authorities, Versions, ['tlsv1.3']) + end, + Opts1 = set_opt_new(new, cacertfile, <<>>, CaCertFile, Opts), + Opts2 = set_opt_new(Where2, certificate_authorities, Role =:= server, CA, Opts1), + Opts2#{cacerts => CaCerts}. + +opt_tickets(UserOpts, #{versions := Versions} = Opts, #{role := client}) -> + {_, SessionTickets} = get_opt_of(session_tickets, [disabled,manual,auto], disabled, UserOpts, Opts), + assert_version_dep(SessionTickets =/= disabled, session_tickets, Versions, ['tlsv1.3']), + + {_, UseTicket} = get_opt_list(use_ticket, undefined, UserOpts, Opts), + option_error(UseTicket =:= [], use_ticket, UseTicket), + option_incompatible(UseTicket =/= undefined andalso SessionTickets =/= manual, + [{use_ticket, UseTicket}, {session_tickets, SessionTickets}]), + + {_, EarlyData} = get_opt_bin(early_data, undefined, UserOpts, Opts), + option_incompatible(is_binary(EarlyData) andalso SessionTickets =:= disabled, + [early_data, {session_tickets, disabled}]), + option_incompatible(is_binary(EarlyData) andalso SessionTickets =:= manual andalso UseTicket =:= undefined, + [early_data, {session_tickets, manual}, {use_ticket, undefined}]), + + assert_server_only(anti_replay, UserOpts), + assert_server_only(stateless_tickets_seed, UserOpts), + Opts#{session_tickets => SessionTickets, use_ticket => UseTicket, early_data => EarlyData}; +opt_tickets(UserOpts, #{versions := Versions} = Opts, #{role := server}) -> + {_, SessionTickets} = + get_opt_of(session_tickets, + [disabled, stateful, stateless, stateful_with_cert, stateless_with_cert], + disabled, + UserOpts, + Opts), + assert_version_dep(SessionTickets =/= disabled, session_tickets, Versions, ['tlsv1.3']), + + {_, EarlyData} = get_opt_of(early_data, [enabled, disabled], disabled, UserOpts, Opts), + option_incompatible(SessionTickets =:= disabled andalso EarlyData =:= enabled, + [early_data, {session_tickets, disabled}]), + + Stateless = lists:member(SessionTickets, [stateless, stateless_with_cert]), + + AntiReplay = + case get_opt(anti_replay, undefined, UserOpts, Opts) of + {_, undefined} -> undefined; + {_,AR} when not Stateless -> + option_incompatible([{anti_replay, AR}, {session_tickets, SessionTickets}]); + {_,'10k'} -> {10, 5, 72985}; %% n = 10000 p = 0.030003564 (1 in 33) m = 72985 (8.91KiB) k = 5 + {_,'100k'} -> {10, 5, 729845}; %% n = 10000 p = 0.03000428 (1 in 33) m = 729845 (89.09KiB) k = 5 + {_, {_,_,_} = AR} -> AR; + {_, AR} -> option_error(anti_replay, AR) + end, + + {_, STS} = get_opt_bin(stateless_tickets_seed, undefined, UserOpts, Opts), + option_incompatible(STS =/= undefined andalso not Stateless, + [stateless_tickets_seed, {session_tickets, SessionTickets}]), + + assert_client_only(use_ticket, UserOpts), + Opts#{session_tickets => SessionTickets, early_data => EarlyData, + anti_replay => AntiReplay, stateless_tickets_seed => STS}. + +opt_stapling(UserOpts, #{versions := _Versions} = Opts, #{role := client}) -> + {Stapling, Nonce} = + case get_opt(stapling, ?DEFAULT_STAPLING_OPT, UserOpts, Opts) of + {old, StaplingMap} when is_map(StaplingMap) -> + {true, maps:get(ocsp_nonce, StaplingMap, ?DEFAULT_OCSP_NONCE_OPT)}; + {_, staple} -> + {true, ?DEFAULT_OCSP_NONCE_OPT}; + {_, no_staple} -> + {false, ignore}; + {_, Map} when is_map(Map) -> + {true, maps:get(ocsp_nonce, Map, ?DEFAULT_OCSP_NONCE_OPT)}; + {_, Value} -> + option_error(stapling, Value) + end, + case Stapling of + true -> + Opts#{stapling => + #{ocsp_nonce => Nonce}}; + false -> + Opts + end; +opt_stapling(UserOpts, Opts, #{role := server}) -> + assert_client_only(stapling, UserOpts), + Opts. + +opt_sni(UserOpts, #{versions := _Versions} = Opts, #{role := server}) -> + {_, SniHosts} = get_opt_list(sni_hosts, [], UserOpts, Opts), + %% Postpone option checking until all other options are checked FIXME + Check = fun({[_|_], SO}) when is_list(SO) -> + case proplists:get_value(sni_hosts, SO, undefined) of + undefined -> ok; + Recursive -> option_error(sni_hosts, Recursive) + end; + (HostOpts) -> option_error(sni_hosts, HostOpts) + end, + [Check(E) || E <- SniHosts], + + {Where, SniFun0} = get_opt_fun(sni_fun, 1, undefined, UserOpts, Opts), + + option_incompatible(is_function(SniFun0) andalso SniHosts =/= [] andalso Where =:= new, + [sni_fun, sni_hosts]), + assert_client_only(server_name_indication, UserOpts), + + SniFun = case SniFun0 =:= undefined of + true -> fun(Host) -> proplists:get_value(Host, SniHosts) end; + false -> SniFun0 + end, + + Opts#{sni_fun => SniFun}; +opt_sni(UserOpts, #{versions := _Versions} = Opts, #{role := client} = Env) -> + %% RFC 6066, Section 3: Currently, the only server names supported are + %% DNS hostnames + %% case inet_parse:domain(Value) of + %% false -> + %% throw({error, {options, {{Opt, Value}}}}); + %% true -> + %% Value + %% end; + %% + %% But the definition seems very diffuse, so let all strings through + %% and leave it up to public_key to decide... + SNI = case get_opt(server_name_indication, unbound, UserOpts, Opts) of + {_, unbound} -> server_name_indication_default(maps:get(host, Env, undefined)); + {_, [_|_] = SN} -> SN; + {_, disable} -> disable; + {_, SN} -> option_error(server_name_indication, SN) + end, + assert_server_only(sni_fun, UserOpts), + assert_server_only(sni_hosts, UserOpts), + Opts#{server_name_indication => SNI}. + +server_name_indication_default(Host) when is_list(Host) -> + %% SNI should not contain a trailing dot that a hostname may + string:strip(Host, right, $.); +server_name_indication_default(_) -> + undefined. + +opt_signature_algs(UserOpts, #{versions := Versions} = Opts, _Env) -> + [TlsVersion|_] = TlsVsns = [ssl:tls_version(V) || V <- Versions], + case ?TLS_GTE(TlsVersion, ?TLS_1_2) of + true -> + opt_signature_algs_valid(UserOpts, Opts, TlsVsns); + false -> + opt_signature_algs_not_valid(UserOpts, Opts) + end. + +opt_signature_algs_valid(UserOpts, Opts, [TlsVersion| _] = TlsVsns)-> + AlgCertSchemes0 = valid_signature_algs_cert(Opts, UserOpts, TlsVersion), + {AlgScemes, AlgCertSchemes} = valid_signature_algs(AlgCertSchemes0, Opts, UserOpts, TlsVsns), + Opts#{signature_algs => AlgScemes, signature_algs_cert => AlgCertSchemes}. + +valid_signature_algs_cert(#{versions := Versions} = Opts, UserOpts, TlsVersion) -> + case get_opt_list(signature_algs_cert, undefined, UserOpts, Opts) of + {new, Schemes} -> + assert_version_dep(signature_algs_cert, Versions, ['tlsv1.2', 'tlsv1.3']), + SAC0 = handle_signature_algorithms_option(Schemes, TlsVersion), + option_error(SAC0 =:= [], no_supported_signature_schemes, + {signature_algs_cert, Schemes}), + SAC0; + {_, Schemes} -> + Schemes + end. +valid_signature_algs(AlgCertSchemes0, #{versions := Versions} = Opts, UserOpts, [TlsVersion| _] = TlsVsns) -> + case get_opt_list(signature_algs, undefined, UserOpts, Opts) of + {default, undefined} -> + %% Smooth upgrade path allow rsa_pkcs1_sha1 for signatures_algs_cert + %% by default as long as signature_algs is set to default + DefAlgs0 = tls_v1:default_signature_algs(TlsVsns), + DefAlgs = handle_hashsigns_option(DefAlgs0, TlsVersion), + AlgCertSchemes = case AlgCertSchemes0 of + undefined -> + [default | DefAlgs ++ sha_rsa(TlsVersion)]; + _ -> + AlgCertSchemes0 + end, + {DefAlgs, AlgCertSchemes}; + {new, Algs} -> + assert_version_dep(signature_algs, Versions, ['tlsv1.2', 'tlsv1.3']), + SAlgs = handle_hashsigns_option(Algs, TlsVersion), + option_error(SAlgs =:= [], no_supported_algorithms, {signature_algs, Algs}), + AlgCertSchemes = case AlgCertSchemes0 of + %% If user sets signature_algs, signature_algs_cert default should + %% be undefined. + [default |_] -> + undefined; + AlgCertSchemes0 -> + AlgCertSchemes0 + end, + {SAlgs, AlgCertSchemes}; + {old, Algs} -> + {Algs, AlgCertSchemes0} + end. + +opt_signature_algs_not_valid(UserOpts, #{versions := Versions} = Opts0) -> + Opts = + case get_opt_list(signature_algs, undefined, UserOpts, Opts0) of + {default, undefined} -> + Opts0#{signature_algs => undefined}; + {old, _} -> + Opts0; + _ -> + option_incompatible([signature_algs, {versions, Versions}]) + end, + case get_opt_list(signature_algs_cert, undefined, UserOpts, Opts) of + {default, undefined} -> + Opts#{signature_algs_cert => undefined}; + {old, _} -> + Opts; + _ -> + option_incompatible([signature_algs_cert, {versions, Versions}]) + end. + +sha_rsa(?TLS_1_2) -> + [{sha, rsa}]; +sha_rsa(?TLS_1_3) -> + [rsa_pkcs1_sha1]. + +opt_alpn(UserOpts, #{versions := Versions} = Opts, #{role := server}) -> + {_, APP} = get_opt_list(alpn_preferred_protocols, undefined, UserOpts, Opts), + validate_protocols(is_list(APP), alpn_preferred_protocols, APP), + + {Where, NPA} = get_opt_list(next_protocols_advertised, undefined, UserOpts, Opts), + validate_protocols(is_list(NPA), next_protocols_advertised, NPA), + assert_version_dep(is_list(NPA), next_protocols_advertised, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + + assert_client_only(alpn_advertised_protocols, UserOpts), + assert_client_only(client_preferred_next_protocols, UserOpts), + + Opts1 = set_opt_new(Where, next_protocols_advertised, undefined, NPA, Opts), + Opts1#{alpn_preferred_protocols => APP}; +opt_alpn(UserOpts, #{versions := Versions} = Opts, #{role := client}) -> + {_, AAP} = get_opt_list(alpn_advertised_protocols, undefined, UserOpts, Opts), + validate_protocols(is_list(AAP), alpn_advertised_protocols, AAP), + + {Where, NPS} = case get_opt(client_preferred_next_protocols, undefined, UserOpts, Opts) of + {new, CPNP} -> + assert_version_dep(client_preferred_next_protocols, + Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + {new, make_next_protocol_selector(CPNP)}; + CPNP -> + CPNP + end, + + validate_protocols(is_list(NPS), client_preferred_next_protocols, NPS), + + assert_server_only(alpn_preferred_protocols, UserOpts), + assert_server_only(next_protocols_advertised, UserOpts), + + Opts1 = set_opt_new(Where, next_protocol_selector, undefined, NPS, Opts), + Opts1#{alpn_advertised_protocols => AAP}. + + +make_next_protocol_selector(undefined) -> + undefined; +make_next_protocol_selector({Precedence, PrefProtcol} = V) -> + option_error(not is_list(PrefProtcol), client_preferred_next_protocols, V), + make_next_protocol_selector({Precedence, PrefProtcol, ?NO_PROTOCOL}); +make_next_protocol_selector({Precedence, AllProtocols, DefP} = V) -> + option_error(not is_list(AllProtocols), client_preferred_next_protocols, V), + option_error(not (is_binary(DefP) andalso byte_size(DefP) < 256), client_preferred_next_protocols, V), + validate_protocols(true, client_preferred_next_protocols, AllProtocols), + case Precedence of + client -> + fun(Advertised) -> + Search = fun(P) -> lists:member(P, Advertised) end, + case lists:search(Search, AllProtocols) of + false -> DefP; + {value, Preferred} -> Preferred + end + end; + server -> + fun(Advertised) -> + Search = fun(P) -> lists:member(P, AllProtocols) end, + case lists:search(Search, Advertised) of + false -> DefP; + {value, Preferred} -> Preferred + end + end; + Value -> + option_error(client_preferred_next_protocols, {invalid_precedence, Value}) + end; +make_next_protocol_selector(What) -> + option_error(client_preferred_next_protocols, What). + +validate_protocols(false, _Opt, _List) -> ok; +validate_protocols(true, Opt, List) -> + Check = fun(Bin) -> + IsOK = is_binary(Bin) andalso byte_size(Bin) > 0 andalso byte_size(Bin) < 256, + option_error(not IsOK, Opt, {invalid_protocol, Bin}) + end, + lists:foreach(Check, List). + +opt_mitigation(UserOpts, #{versions := Versions} = Opts, _Env) -> + DefBeast = case ?TLS_GT(lists:last(Versions), ?TLS_1_0) of + true -> disabled; + false -> one_n_minus_one + end, + {Where1, BM} = get_opt_of(beast_mitigation, [disabled, one_n_minus_one, zero_n], + DefBeast, UserOpts, Opts), + assert_version_dep(Where1 =:= new, beast_mitigation, Versions, ['tlsv1']), + + {Where2, PC} = get_opt_bool(padding_check, true, UserOpts, Opts), + assert_version_dep(Where2 =:= new, padding_check, Versions, ['tlsv1']), + + %% Use 'new' we need to check for non default 'one_n_minus_one' + Opts1 = if + DefBeast =:= one_n_minus_one, BM =:= disabled -> + Opts#{beast_mitigation => BM}; + true -> + set_opt_new(new, beast_mitigation, disabled, BM, Opts) + end, + set_opt_new(Where2, padding_check, true, PC, Opts1). + +opt_server(UserOpts, #{versions := Versions, log_level := LogLevel} = Opts, #{role := server}) -> + {_, ECC} = get_opt_bool(honor_ecc_order, false, UserOpts, Opts), + + {_, Cipher} = get_opt_bool(honor_cipher_order, false, UserOpts, Opts), + + {Where1, Cookie} = get_opt_bool(cookie, true, UserOpts, Opts), + assert_version_dep(Where1 =:= new, cookie, Versions, ['tlsv1.3']), + + {Where2, ReNeg} = get_opt_bool(client_renegotiation, true, UserOpts, Opts), + assert_version_dep(Where2 =:= new, client_renegotiation, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), + + Opts1 = case get_opt(dh, undefined, UserOpts, Opts) of + {Where, DH} when is_binary(DH) -> + warn_override(Where, UserOpts, dh, [dhfile], LogLevel), + Opts#{dh => DH}; + {new, DH} -> + option_error(dh, DH); + {_, undefined} -> + case get_opt_file(dhfile, unbound, UserOpts, Opts) of + {default, unbound} -> Opts; + {_, DHFile} -> Opts#{dhfile => DHFile} + end + end, + + Opts1#{honor_ecc_order => ECC, honor_cipher_order => Cipher, + cookie => Cookie, client_renegotiation => ReNeg}; +opt_server(UserOpts, Opts, #{role := client}) -> + assert_server_only(honor_ecc_order, UserOpts), + assert_server_only(honor_cipher_order, UserOpts), + assert_server_only(cookie, UserOpts), + assert_server_only(client_renegotiation, UserOpts), + assert_server_only(dh, UserOpts), + assert_server_only(dhfile, UserOpts), + Opts. + +opt_client(UserOpts, #{versions := Versions} = Opts, #{role := client}) -> + {Where, FB} = get_opt_bool(fallback, false, UserOpts, Opts), + assert_version_dep(Where =:= new, fallback, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + + {_, CHC} = get_opt_list(customize_hostname_check, [], UserOpts, Opts), + + ValidMFL = [undefined, ?MAX_FRAGMENT_LENGTH_BYTES_1, ?MAX_FRAGMENT_LENGTH_BYTES_2, %% RFC 6066, Section 4 + ?MAX_FRAGMENT_LENGTH_BYTES_3, ?MAX_FRAGMENT_LENGTH_BYTES_4], + {_, MFL} = get_opt_of(max_fragment_length, ValidMFL, undefined, UserOpts, Opts), + + Opts#{fallback => FB, customize_hostname_check => CHC, max_fragment_length => MFL}; +opt_client(UserOpts, Opts, #{role := server}) -> + assert_client_only(fallback, UserOpts), + assert_client_only(customize_hostname_check, UserOpts), + assert_client_only(max_fragment_length, UserOpts), + Opts#{customize_hostname_check => []}. + +opt_renegotiate(UserOpts, #{versions := Versions} = Opts, _Env) -> + {Where1, KUA} = get_opt_pos_int(key_update_at, ?KEY_USAGE_LIMIT_AES_GCM, UserOpts, Opts), + assert_version_dep(Where1 =:= new, key_update_at, Versions, ['tlsv1.3']), + + %% Undocumented, old ? + {_, RA0} = get_opt_pos_int(renegotiate_at, ?DEFAULT_RENEGOTIATE_AT, UserOpts, Opts), + RA = min(RA0, ?DEFAULT_RENEGOTIATE_AT), %% Override users choice without notifying ?? + + {Where3, SR} = get_opt_bool(secure_renegotiate, true, UserOpts, Opts), + assert_version_dep(Where3 =:= new, secure_renegotiate, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + + Opts#{secure_renegotiate => SR, key_update_at => KUA, renegotiate_at => RA}. + +opt_reuse_sessions(UserOpts, #{versions := Versions} = Opts, #{role := client}) -> + {Where1, RUSS} = get_opt_of(reuse_sessions, [true, false, save], true, UserOpts, Opts), + + {Where2, RS} = RST = get_opt(reuse_session, undefined, UserOpts, Opts), + case RST of + {new, Bin} when is_binary(Bin) -> ok; + {new, {B1,B2}} when is_binary(B1), is_binary(B2) -> ok; + {new, Bad} -> option_error(reuse_session, Bad); + {_, _} -> ok + end, + + assert_version_dep(Where1 =:= new, reuse_sessions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + assert_version_dep(Where2 =:= new, reuse_session, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + Opts#{reuse_sessions => RUSS, reuse_session => RS}; +opt_reuse_sessions(UserOpts, #{versions := Versions} = Opts, #{role := server}) -> + {Where1, RUSS} = get_opt_bool(reuse_sessions, true, UserOpts, Opts), + + DefRS = fun(_, _, _, _) -> true end, + {Where2, RS} = get_opt_fun(reuse_session, 4, DefRS, UserOpts, Opts), + + assert_version_dep(Where1 =:= new, reuse_sessions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + assert_version_dep(Where2 =:= new, reuse_session, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + Opts#{reuse_sessions => RUSS, reuse_session => RS}. + +opt_identity(UserOpts, Opts, _Env) -> + PSK = handle_psk(UserOpts, Opts), + SRP = handle_srp(UserOpts, Opts, PSK), + Lookup = handle_user_lookup(UserOpts, Opts), + Opts#{psk_identity => PSK, srp_identity => SRP, user_lookup_fun => Lookup}. + + +handle_psk(UserOpts, #{versions := Versions} = Opts) -> + case get_opt_list(psk_identity, undefined, UserOpts, Opts) of + {new, PSK0} -> + PSK1 = unicode:characters_to_binary(PSK0), + PSKSize = byte_size(PSK1), + assert_version_dep(psk_identity, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + option_error(not (0 < PSKSize andalso PSKSize < 65536), + psk_identity, {psk_identity, PSK0}), + PSK1; + {_, PSK0} -> + PSK0 + end. + +handle_srp(UserOpts, #{versions := Versions} = Opts, PSK) -> + case get_opt(srp_identity, undefined, UserOpts, Opts) of + {new, {S1, S2}} when is_list(S1), is_list(S2) -> + User = unicode:characters_to_binary(S1), + UserSize = byte_size(User), + assert_version_dep(srp_identity, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + option_error(not (0 < UserSize andalso UserSize < 65536), + srp_identity, {srp_identity, PSK}), + {User, unicode:characters_to_binary(S2)}; + {new, Err} -> + option_error(srp_identity, Err); + {_, SRP0} -> + SRP0 + end. + +handle_user_lookup(UserOpts, #{versions := Versions} = Opts) -> + case get_opt(user_lookup_fun, undefined, UserOpts, Opts) of + {new, {Fun, _} = Lookup} when is_function(Fun, 3) -> + assert_version_dep(user_lookup_fun, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + Lookup; + {new, Lookup} -> + option_error(user_lookup_fun, Lookup); + {_, Lookup} -> + Lookup + end. + + +opt_supported_groups(UserOpts, #{versions := TlsVsns} = Opts, _Env) -> + SG = case get_opt_list(supported_groups, undefined, UserOpts, Opts) of + {default, undefined} -> + handle_supported_groups_option(ssl:groups(default)); + {new, SG0} -> + assert_version_dep(supported_groups, TlsVsns, ['tlsv1.3']), + handle_supported_groups_option(SG0); + {old, SG0} -> + SG0 + end, + + CPHS = case get_opt_list(ciphers, [], UserOpts, Opts) of + {old, CPS0} -> CPS0; + {_, CPS0} -> handle_cipher_option(CPS0, TlsVsns) + end, + + ECCS = try assert_version_dep(eccs, TlsVsns, ['tlsv1.2', 'tlsv1.1', 'tlsv1']) of + _ -> + case get_opt_list(eccs, undefined, UserOpts, Opts) of + {old, ECCS0} -> ECCS0; + {default, _} -> handle_eccs_option(tls_v1:ec_curves(default, 'tlsv1.2')); + {new, ECCS0} -> handle_eccs_option(ECCS0) + end + catch + throw:_ -> [] + end, + Opts#{ciphers => CPHS, eccs => ECCS, supported_groups => SG}. + +opt_crl(UserOpts, Opts, _Env) -> + {_, Check} = get_opt_of(crl_check, [best_effort, peer, true, false], false, UserOpts, Opts), + Cache = case get_opt(crl_cache, {ssl_crl_cache, {internal, []}}, UserOpts, Opts) of + {_, {Cb, {_Handle, Options}} = Value} when is_atom(Cb), is_list(Options) -> + Value; + {_, Err} -> + option_error(crl_cache, Err) + end, + Opts#{crl_check => Check, crl_cache => Cache}. + +opt_handshake(UserOpts, Opts, _Env) -> + {_, HS} = get_opt_of(handshake, [hello, full], full, UserOpts, Opts), + + {_, MHSS} = get_opt_int(max_handshake_size, 1, ?MAX_UNIT24, ?DEFAULT_MAX_HANDSHAKE_SIZE, + UserOpts, Opts), + + Opts#{handshake => HS, max_handshake_size => MHSS}. + +opt_use_srtp(UserOpts, #{protocol := Protocol} = Opts, _Env) -> + UseSRTP = case get_opt_map(use_srtp, undefined, UserOpts, Opts) of + {old, UseSRTP0} -> + UseSRTP0; + {default, undefined} -> + undefined; + {new, UseSRTP1} -> + assert_protocol_dep(use_srtp, Protocol, [dtls]), + validate_use_srtp(UseSRTP1) + end, + case UseSRTP of + #{} -> Opts#{use_srtp => UseSRTP}; + _ -> Opts + end. + +validate_use_srtp(#{protection_profiles := [_|_] = PPs} = UseSRTP) -> + case maps:keys(UseSRTP) -- [protection_profiles, mki] of + [] -> ok; + Extra -> option_error(use_srtp, {unknown_parameters, Extra}) + end, + IsValidProfile = fun(<<_, _>>) -> true; (_) -> false end, + case lists:all(IsValidProfile, PPs) of + true -> ok; + false -> option_error(use_srtp, {invalid_protection_profiles, PPs}) + end, + case UseSRTP of + #{mki := MKI} when not is_binary(MKI) -> + option_error(use_srtp, {invalid_mki, MKI}); + #{mki := _} -> + UseSRTP; + #{} -> + UseSRTP#{mki => <<>>} + end; + +validate_use_srtp(#{} = UseSRTP) -> + option_error(use_srtp, {no_protection_profiles, UseSRTP}). + + +opt_process(UserOpts, Opts0, _Env) -> + Opts1 = set_opt_list(receiver_spawn_opts, [], UserOpts, Opts0), + Opts2 = set_opt_list(sender_spawn_opts, [], UserOpts, Opts1), + %% {_, SSO} = get_opt_list(sender_spawn_opts, [], UserOpts, Opts), + %% Opts = Opts1#{receiver_spawn_opts => RSO, sender_spawn_opts => SSO}, + set_opt_int(hibernate_after, 0, infinity, infinity, UserOpts, Opts2). + +%%%% + +get_opt(Opt, Default, UserOpts, Opts) -> + case maps:get(Opt, UserOpts, unbound) of + unbound -> + case maps:get(maybe_map_key_internal(Opt), Opts, unbound) of + unbound -> %% Uses default value + {default, Default}; + Value -> %% Uses already set value (merge) + {old, Value} end; - {ok, Args} -> - Args + Value -> %% Uses new user option + {new, Value} end. -session_lifetime(_Role) -> - case application:get_env(ssl, session_lifetime) of - {ok, Time} when is_integer(Time) -> - Time; - _ -> - ?'24H_in_sec' +get_opt_of(Opt, Valid, Default, UserOpts, Opts) -> + case get_opt(Opt, Default, UserOpts, Opts) of + {new, Value} = Res -> + case lists:member(Value, Valid) of + true -> Res; + false -> option_error(Opt, Value) + end; + Res -> + Res end. -max_session_cache_size(client) -> - case application:get_env(ssl, session_cache_client_max) of - {ok, Size} when is_integer(Size) -> - Size; - _ -> - ?DEFAULT_MAX_SESSION_CACHE +get_opt_bool(Opt, Default, UserOpts, Opts) -> + case get_opt(Opt, Default, UserOpts, Opts) of + {_, Value} = Res when is_boolean(Value) -> Res; + {_, Value} -> option_error(Opt, Value) + end. + +get_opt_pos_int(Opt, Default, UserOpts, Opts) -> + get_opt_int(Opt, 1, infinity, Default, UserOpts, Opts). + +get_opt_int(Opt, Min, Max, Default, UserOpts, Opts) -> + case get_opt(Opt, Default, UserOpts, Opts) of + {_, Value} = Res when is_integer(Value), Min =< Value, Value =< Max -> + Res; + {_, Value} = Res when Value =:= infinity, Max =:= infinity -> + Res; + {_, Value} -> + option_error(Opt, Value) + end. + +get_opt_fun(Opt, Arity, Default, UserOpts, Opts) -> + case get_opt(Opt, Default, UserOpts, Opts) of + {_, Fun} = Res when is_function(Fun, Arity) -> Res; + {new, Err} -> option_error(Opt, Err); + Res -> Res + end. + +get_opt_list(Opt, Default, UserOpts, Opts) -> + case get_opt(Opt, Default, UserOpts, Opts) of + {new, Err} when not is_list(Err) -> option_error(Opt, Err); + Res -> Res + end. + +get_opt_bin(Opt, Default, UserOpts, Opts) -> + case get_opt(Opt, Default, UserOpts, Opts) of + {new, Err} when not is_binary(Err) -> option_error(Opt, Err); + Res -> Res + end. + +get_opt_file(Opt, Default, UserOpts, Opts) -> + case get_opt(Opt, Default, UserOpts, Opts) of + {new, File} -> {new, validate_filename(File, Opt)}; + Res -> Res + end. + +set_opt_bool(Opt, Default, UserOpts, Opts) -> + case maps:get(Opt, UserOpts, Default) of + Default -> Opts; + Value when is_boolean(Value) -> Opts#{Opt => Value}; + Value -> option_error(Opt, Value) + end. + +get_opt_map(Opt, Default, UserOpts, Opts) -> + case get_opt(Opt, Default, UserOpts, Opts) of + {new, Err} when not is_map(Err) -> option_error(Opt, Err); + Res -> Res + end. + +set_opt_int(Opt, Min, Max, Default, UserOpts, Opts) -> + case maps:get(Opt, UserOpts, Default) of + Default -> + Opts; + Value when is_integer(Value), Min =< Value, Value =< Max -> + Opts#{Opt => Value}; + Value when Value =:= infinity, Max =:= infinity -> + Opts#{Opt => Value}; + Value -> + option_error(Opt, Value) + end. + +set_opt_list(Opt, Default, UserOpts, Opts) -> + case maps:get(Opt, UserOpts, []) of + Default -> + Opts; + List when is_list(List) -> + Opts#{Opt => List}; + Value -> + option_error(Opt, Value) + end. + +set_opt_new(new, Opt, Default, Value, Opts) + when Default =/= Value -> + Opts#{Opt => Value}; +set_opt_new(_, _, _, _, Opts) -> + Opts. + +%%%% + +default_cb_info(tls) -> + {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive}; +default_cb_info(dtls) -> + {gen_udp, udp, udp_closed, udp_error, udp_passive}. + +handle_cb_info({V1, V2, V3, V4}) -> + {V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "_passive")}; +handle_cb_info(CbInfo) when tuple_size(CbInfo) =:= 5 -> + CbInfo; +handle_cb_info(CbInfo) -> + option_error(cb_info, CbInfo). + +handle_option_cb_info(Options, Protocol) -> + CbInfo = proplists:get_value(cb_info, Options, default_cb_info(Protocol)), + handle_cb_info(CbInfo). + +maybe_map_key_internal(client_preferred_next_protocols) -> + next_protocol_selector; +maybe_map_key_internal(K) -> + K. + +split_options(Opts0, AllOptions) -> + Opts1 = proplists:expand([{binary, [{mode, binary}]}, + {list, [{mode, list}]}], Opts0), + Opts2 = handle_option_format(Opts1, []), + %% Remove deprecated ssl_imp option + Opts = proplists:delete(ssl_imp, Opts2), + + DeleteUserOpts = fun(Key, PropList) -> proplists:delete(Key, PropList) end, + AllOpts = [cb_info, client_preferred_next_protocols] ++ AllOptions, + SockOpts = lists:foldl(DeleteUserOpts, Opts, AllOpts), + {Opts -- SockOpts, SockOpts}. + +assert_server_only(Option, Opts) -> + Value = maps:get(Option, Opts, undefined), + role_error(Value =/= undefined, server_only, Option). +assert_client_only(Option, Opts) -> + Value = maps:get(Option, Opts, undefined), + role_error(Value =/= undefined, client_only, Option). + +assert_server_only(client, Bool, Option) -> + role_error(Bool, server_only, Option); +assert_server_only(_, _, _) -> + ok. + +role_error(false, _ErrorDesc, _Option) -> + ok; +role_error(true, ErrorDesc, Option) + when ErrorDesc =:= client_only; ErrorDesc =:= server_only -> + throw_error({option, ErrorDesc, Option}). + +option_incompatible(false, _Options) -> ok; +option_incompatible(true, Options) -> option_incompatible(Options). + +-spec option_incompatible(_) -> no_return(). +option_incompatible(Options) -> + throw_error({options, incompatible, Options}). + +option_error(false, _, _What) -> true; +option_error(true, Tag, What) -> option_error(Tag,What). + +-spec option_error(_,_) -> no_return(). +option_error(Tag, What) -> + throw_error({options, {Tag, What}}). + +-spec throw_error(_) -> no_return(). +throw_error(Err) -> + throw({error, Err}). + +assert_protocol_dep(Option, Protocol, AllowedProtos) -> + case lists:member(Protocol, AllowedProtos) of + true -> ok; + false -> option_incompatible([Option, {protocol, Protocol}]) + end. + +assert_version_dep(Option, Vsns, AllowedVsn) -> + assert_version_dep(true, Option, Vsns, AllowedVsn). + +assert_version_dep(false, _, _, _) -> true; +assert_version_dep(true, Option, SSLVsns, AllowedVsn) -> + case is_dtls_configured(SSLVsns) of + true -> %% TODO: Check option dependency for DTLS + true; + false -> + APIVsns = lists:map(fun tls_record:protocol_version/1, SSLVsns), + Set1 = sets:from_list(APIVsns), + Set2 = sets:from_list(AllowedVsn), + case sets:size(sets:intersection(Set1, Set2)) > 0 of + true -> ok; + false -> option_incompatible([Option, {versions, APIVsns}]) + end + end. + +warn_override(new, UserOpts, NewOpt, OldOpts, LogLevel) -> + Check = fun(Key) -> maps:is_key(Key,UserOpts) end, + case lists:filter(Check, OldOpts) of + [] -> ok; + Ignored -> + Desc = lists:flatten(io_lib:format("Options ~w are ignored", [Ignored])), + Reas = lists:flatten(io_lib:format("Option ~w is set", [NewOpt])), + ssl_logger:log(notice, LogLevel, #{description => Desc, reason => Reas}, ?LOCATION) end; -max_session_cache_size(server) -> - case application:get_env(ssl, session_cache_server_max) of - {ok, Size} when is_integer(Size) -> - Size; +warn_override(_, _UserOpts, _NewOpt, _OldOpts, _LogLevel) -> + ok. + +is_dtls_configured(Versions) -> + lists:any(fun (Ver) -> ?DTLS_1_X(Ver) end, Versions). + +handle_hashsigns_option(Value, Version) -> + try + if ?TLS_GTE(Version, ?TLS_1_3) -> + tls_v1:signature_schemes(Version, Value); + (Version =:= ?TLS_1_2) -> + tls_v1:signature_algs(Version, Value); + true -> + undefined + end + catch error:function_clause -> + option_error(signature_algs, Value) + end. + +handle_signature_algorithms_option(Value, Version) -> + try tls_v1:signature_schemes(Version, Value) + catch error:function_clause -> + option_error(signature_algs_cert, Value) + end. + +validate_filename(FN, _Option) when is_binary(FN), FN =/= <<>> -> + FN; +validate_filename([_|_] = FN, _Option) -> + Enc = file:native_name_encoding(), + unicode:characters_to_binary(FN, unicode, Enc); +validate_filename(FN, Option) -> + option_error(Option, FN). + +validate_server_cert_opts(_Opts, #{validate_certs_or_anon_ciphers := false}) -> + ok; +validate_server_cert_opts(#{certs_keys := [_|_]=CertsKeys, ciphers := CPHS, versions := Versions}, _) -> + validate_certs_or_anon_ciphers(CertsKeys, CPHS, Versions); +validate_server_cert_opts(#{ciphers := CPHS, versions := Versions}, _) -> + validate_anon_ciphers(CPHS, Versions). + +validate_certs_or_anon_ciphers(CertsKeys, Ciphers, Versions) -> + CheckCertsAndKeys = + fun(Map) -> + (maps:is_key(cert, Map) orelse maps:is_key(certfile, Map)) + andalso (maps:is_key(key, Map) orelse maps:is_key(keyfile, Map)) + end, + case lists:any(CheckCertsAndKeys, CertsKeys) of + true -> ok; + false -> validate_anon_ciphers(Ciphers, Versions) + end. + +validate_anon_ciphers(Ciphers, Versions) -> + MakeSet = fun(Version, Acc) -> + Set = sets:from_list(ssl_cipher:anonymous_suites(Version)), + sets:union(Set, Acc) + end, + Anonymous = lists:foldl(MakeSet, sets:new(), Versions), + CiphersSet = sets:from_list(Ciphers, [{version,2}]), + case sets:is_disjoint(Anonymous, CiphersSet) of + false -> ok; + true -> option_error(certs_keys, cert_and_key_required) + end. + +%% Do not allow configuration of TLS 1.3 with a gap where TLS 1.2 is not supported +%% as that configuration can trigger the built in version downgrade protection +%% mechanism and the handshake can fail with an Illegal Parameter alert. +tls_validate_version_gap(Versions) -> + case lists:member('tlsv1.3', Versions) of + true when length(Versions) >= 2 -> + case lists:member('tlsv1.2', Versions) of + true -> + Versions; + false -> + throw({error, {options, missing_version, {'tlsv1.2', {versions, Versions}}}}) + end; + _ -> + Versions + end. + +emulated_options(undefined, undefined, Protocol, Opts) -> + case Protocol of + tls -> + tls_socket:emulated_options(Opts); + dtls -> + dtls_socket:emulated_options(Opts) + end; +emulated_options(Transport, Socket, Protocol, Opts) -> + EmulatedOptions = tls_socket:emulated_options(), + {ok, Original} = tls_socket:getopts(Transport, Socket, EmulatedOptions), + {Inet, Emulated0} = emulated_options(undefined, undefined, Protocol, Opts), + {Inet, lists:ukeymerge(1, Emulated0, Original)}. + +handle_cipher_option(Value, Versions) when is_list(Value) -> + try binary_cipher_suites(Versions, Value) of + Suites -> + Suites + catch + exit:_ -> + option_error(ciphers, Value); + error:_-> + option_error(ciphers, Value) + end. + +binary_cipher_suites([?TLS_1_3], []) -> + %% Defaults to all supported suites that does + %% not require explicit configuration TLS-1.3 + %% only mode. + default_binary_suites(exclusive, ?TLS_1_3); +binary_cipher_suites([Version| _], []) -> + %% Defaults to all supported suites that does + %% not require explicit configuration + default_binary_suites(default, Version); +binary_cipher_suites(Versions, [Map|_] = Ciphers0) when is_map(Map) -> + Ciphers = [ssl_cipher_format:suite_map_to_bin(C) || C <- Ciphers0], + binary_cipher_suites(Versions, Ciphers); +binary_cipher_suites(Versions, [Tuple|_] = Ciphers0) when is_tuple(Tuple) -> + Ciphers = [ssl_cipher_format:suite_map_to_bin(tuple_to_map(C)) || C <- Ciphers0], + binary_cipher_suites(Versions, Ciphers); +binary_cipher_suites(Versions, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> + All = all_suites(Versions), + case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, All)] of + [] -> + %% Defaults to all supported suites that does + %% not require explicit configuration + binary_cipher_suites(Versions, []); + Ciphers -> + Ciphers + end; +binary_cipher_suites(Versions, [Head | _] = Ciphers0) when is_list(Head) -> + %% Format: ["RC4-SHA","RC4-MD5"] + Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- Ciphers0], + binary_cipher_suites(Versions, Ciphers); +binary_cipher_suites(Versions, Ciphers0) -> + %% Format: "RC4-SHA:RC4-MD5" + Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- string:lexemes(Ciphers0, ":")], + binary_cipher_suites(Versions, Ciphers). + +default_binary_suites(exclusive, Version) -> + ssl_cipher:filter_suites(tls_v1:exclusive_suites(Version)); +default_binary_suites(default, Version) -> + ssl_cipher:filter_suites(ssl_cipher:suites(Version)). + +all_suites([?TLS_1_3]) -> + tls_v1:exclusive_suites(?TLS_1_3); +all_suites([?TLS_1_3, Version1 |_]) -> + all_suites([?TLS_1_3]) ++ + ssl_cipher:all_suites(Version1) ++ + ssl_cipher:anonymous_suites(Version1); +all_suites([Version|_]) -> + ssl_cipher:all_suites(Version) ++ + ssl_cipher:anonymous_suites(Version). + +tuple_to_map({Kex, Cipher, Mac}) -> + #{key_exchange => Kex, + cipher => Cipher, + mac => Mac, + prf => default_prf}; +tuple_to_map({Kex, Cipher, Mac, Prf}) -> + #{key_exchange => Kex, + cipher => Cipher, + mac => tuple_to_map_mac(Cipher, Mac), + prf => Prf}. + +%% Backwards compatible +tuple_to_map_mac(aes_128_gcm, _) -> + aead; +tuple_to_map_mac(aes_256_gcm, _) -> + aead; +tuple_to_map_mac(chacha20_poly1305, _) -> + aead; +tuple_to_map_mac(_, MAC) -> + MAC. + +handle_eccs_option(Value) when is_list(Value) -> + try tls_v1:ecc_curves(Value) of + Curves -> + option_error(Curves =:= [], eccs, none_valid), + #elliptic_curves{elliptic_curve_list = Curves} + catch + exit:_ -> option_error(eccs, Value); + error:_ -> option_error(eccs, Value) + end. + +handle_supported_groups_option(Value) when is_list(Value) -> + try tls_v1:groups(Value) of + Groups -> + option_error(Groups =:= [], supported_groups, none_valid), + #supported_groups{supported_groups = Groups} + catch + exit:_ -> option_error(supported_groups, Value); + error:_ -> option_error(supported_groups, Value) + end. + +unambiguous_path(Value) -> + AbsName = filename:absname(Value), + UP = case file:read_link(AbsName) of + {ok, PathWithNoLink} -> + case filename:pathtype(PathWithNoLink) of + relative -> + Dirname = filename:dirname(AbsName), + filename:join([Dirname, PathWithNoLink]); + _ -> + PathWithNoLink + end; + _ -> + AbsName + end, + validate_filename(UP, cacertfile). + +%% Assert that basic options are on the format {Key, Value} +%% with a few exceptions and phase out log_alert +handle_option_format([], Acc) -> + lists:reverse(Acc); +handle_option_format([{log_alert, Bool} | Rest], Acc) when is_boolean(Bool) -> + case proplists:get_value(log_level, Acc ++ Rest, undefined) of + undefined -> + handle_option_format(Rest, [{log_level, + map_log_level(Bool)} | Acc]); + _ -> + handle_option_format(Rest, Acc) + end; +handle_option_format([{Key,_} = Opt | Rest], Acc) when is_atom(Key) -> + handle_option_format(Rest, [Opt | Acc]); +%% Handle exceptions +handle_option_format([{raw,_,_,_} = Opt | Rest], Acc) -> + handle_option_format(Rest, [Opt | Acc]); +handle_option_format([inet = Opt | Rest], Acc) -> + handle_option_format(Rest, [Opt | Acc]); +handle_option_format([inet6 = Opt | Rest], Acc) -> + handle_option_format(Rest, [Opt | Acc]); +handle_option_format([Value | _], _) -> + option_error(option_not_a_key_value_tuple, Value). + +map_log_level(true) -> + notice; +map_log_level(false) -> + none. + + +%%==================================================================== +%% Application options (application environment variables) +%%==================================================================== + +application_int(EnvVar, Default) -> + case application:get_env(ssl, EnvVar) of + {ok, Int} when is_integer(Int) -> + Int; + _ -> + Default + end. + +application_list(EnvVar, Default) -> + case application:get_env(ssl, EnvVar) of + {ok, List} when is_list(List) -> + List; _ -> - ?DEFAULT_MAX_SESSION_CACHE + Default + end. + +application_atom(EnvVar, Default) -> + case application:get_env(ssl, EnvVar) of + {ok, Atom} when is_atom(Atom) -> + Atom; + _ -> + Default + end. + +session_cb_init_args(client) -> + case application_list(client_session_cb_init_args, undefined) of + undefined -> + application_list(session_cb_init_args, []); + List -> + List + end; +session_cb_init_args(server) -> + case application_list(server_session_cb_init_args, undefined) of + undefined -> + application_list(session_cb_init_args, []); + List -> + List end. +session_lifetime(_Role) -> + application_int(session_lifetime, ?'24H_in_sec'). + +max_session_cache_size(client) -> + application_int(session_cache_client_max, ?DEFAULT_MAX_SESSION_CACHE); +max_session_cache_size(server) -> + application_int(session_cache_server_max, ?DEFAULT_MAX_SESSION_CACHE). + session_cb_opts(client = Role)-> - case application:get_env(ssl, session_cb, ssl_client_session_cache_db) of + case application_atom(session_cb, ssl_client_session_cache_db) of ssl_client_session_cache_db = ClientCb -> {ClientCb, []}; ClientCb -> {ClientCb, session_cb_init_args(Role)} end; session_cb_opts(server = Role) -> - case application:get_env(ssl, session_cb, ssl_server_session_cache_db) of + case application_atom(session_cb, ssl_server_session_cache_db) of ssl_server_session_cache_db = ServerCb -> {ServerCb, []}; ServerCb -> {ServerCb, session_cb_init_args(Role)} end. -ecdsa_support(#{versions := [?TLS_1_3]}) -> - [secp521r1, - secp384r1, - secp256r1, - brainpoolP512r1, - brainpoolP384r1, - brainpoolP256r1 - ]; -ecdsa_support(_) -> - ssl:eccs() -- [x25519, x448]. +connection_cb(tls) -> + tls_gen_connection; +connection_cb(dtls) -> + dtls_gen_connection. + +%%%-------------------------------------------------------------- +%%% Tracing +%%%-------------------------------------------------------------------- +handle_trace(csp, {call, {?MODULE, opt_stapling, [UserOpts | _]}}, Stack) -> + {format_ocsp_params(UserOpts), Stack}; +handle_trace(csp, {return_from, {?MODULE, opt_stapling, 3}, Return}, Stack) -> + {format_ocsp_params(Return), Stack}. + +format_ocsp_params(Map) -> + Stapling = maps:get(stapling, Map, '?'), + Nonce = maps:get(ocsp_nonce, Map, '?'), + io_lib:format("Stapling = ~W Nonce = ~W", [Stapling, 5, Nonce, 5]). + diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index b46c7640654..f516ef8eb80 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -1330,7 +1330,7 @@ update_ssl_options_from_sni(#{sni_fun := SNIFun} = OrigSSLOptions, SNIHostname) SSLOptions -> VersionsOpt = proplists:get_value(versions, SSLOptions, []), FallBackOptions = filter_for_versions(VersionsOpt, OrigSSLOptions), - ssl:update_options(SSLOptions, server, FallBackOptions) + ssl_config:update_options(SSLOptions, server, FallBackOptions) end. filter_for_versions([], OrigSSLOptions) -> diff --git a/lib/ssl/src/ssl_trace.erl b/lib/ssl/src/ssl_trace.erl index 38650092b17..6b711cb1f9e 100644 --- a/lib/ssl/src/ssl_trace.erl +++ b/lib/ssl/src/ssl_trace.erl @@ -452,7 +452,7 @@ trace_profiles() -> {is_responder_cert, 2}, {find_single_response, 3}, {status, 2}, {match_single_response, 4}, {designated_for_ocsp_signing, 1}]}, - {ssl, [{opt_stapling, 3}]}, + {ssl_config, [{opt_stapling, 3}]}, {ssl_certificate, [{verify_cert_extensions, 5}]}, {ssl_test_lib, [{init_openssl_server, 3}, {openssl_server_loop, 3}]}, {tls_client_connection, [{wait_stapling, 3}]}, diff --git a/lib/ssl/src/tls_client_connection.erl b/lib/ssl/src/tls_client_connection.erl index 4e3f2172c15..77076a0c063 100644 --- a/lib/ssl/src/tls_client_connection.erl +++ b/lib/ssl/src/tls_client_connection.erl @@ -264,11 +264,10 @@ initial_hello({call, From}, {start, {Opts, EmOpts}, Timeout}, ssl_options = OrigSSLOptions, socket_options = SockOpts} = State0) -> try - SslOpts = ssl:update_options(Opts, Role, OrigSSLOptions), + SslOpts = ssl_config:update_options(Opts, Role, OrigSSLOptions), State = ssl_gen_statem:ssl_config(SslOpts, Role, State0), initial_hello({call, From}, {start, Timeout}, - State#state{ssl_options = SslOpts, - socket_options = + State#state{socket_options = ssl_config:new_emulated(EmOpts, SockOpts)}) catch throw:Error -> {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0} diff --git a/lib/ssl/src/tls_client_connection_1_3.erl b/lib/ssl/src/tls_client_connection_1_3.erl index 15aaf155a81..b93fce38596 100644 --- a/lib/ssl/src/tls_client_connection_1_3.erl +++ b/lib/ssl/src/tls_client_connection_1_3.erl @@ -177,7 +177,7 @@ user_hello({call, From}, cancel, State) -> user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, #state{handshake_env = #handshake_env{continue_status = pause} = HSEnv, ssl_options = Options0} = State0) -> - try ssl:update_options(NewOptions, ?CLIENT_ROLE, Options0) of + try ssl_config:update_options(NewOptions, ?CLIENT_ROLE, Options0) of Options -> State = ssl_gen_statem:ssl_config(Options, ?CLIENT_ROLE, State0), {next_state, wait_sh, State#state{recv = State#state.recv#recv{from = From}, diff --git a/lib/ssl/src/tls_dtls_gen_connection.erl b/lib/ssl/src/tls_dtls_gen_connection.erl index 0db2638ae8f..c8880349d53 100644 --- a/lib/ssl/src/tls_dtls_gen_connection.erl +++ b/lib/ssl/src/tls_dtls_gen_connection.erl @@ -266,7 +266,7 @@ user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, #state{static_env = #static_env{role = Role}, handshake_env = HSEnv, ssl_options = Options0} = State0) -> - try ssl:update_options(NewOptions, Role, Options0) of + try ssl_config:update_options(NewOptions, Role, Options0) of Options -> State = ssl_gen_statem:ssl_config(Options, Role, State0), {next_state, hello, diff --git a/lib/ssl/src/tls_server_connection.erl b/lib/ssl/src/tls_server_connection.erl index 5f3d80081af..56f412af852 100644 --- a/lib/ssl/src/tls_server_connection.erl +++ b/lib/ssl/src/tls_server_connection.erl @@ -173,11 +173,10 @@ initial_hello({call, From}, {start, {Opts, EmOpts}, Timeout}, ssl_options = OrigSSLOptions, socket_options = SockOpts} = State0) -> try - SslOpts = ssl:update_options(Opts, Role, OrigSSLOptions), + SslOpts = ssl_config:update_options(Opts, Role, OrigSSLOptions), State = ssl_gen_statem:ssl_config(SslOpts, Role, State0), initial_hello({call, From}, {start, Timeout}, - State#state{ssl_options = SslOpts, - socket_options = ssl_config:new_emulated(EmOpts, SockOpts)}) + State#state{socket_options = ssl_config:new_emulated(EmOpts, SockOpts)}) catch throw:Error -> {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0} end; diff --git a/lib/ssl/src/tls_server_connection_1_3.erl b/lib/ssl/src/tls_server_connection_1_3.erl index 0acc97e2dd7..646fd8ac384 100644 --- a/lib/ssl/src/tls_server_connection_1_3.erl +++ b/lib/ssl/src/tls_server_connection_1_3.erl @@ -167,7 +167,7 @@ user_hello({call, From}, cancel, State) -> user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, #state{handshake_env = #handshake_env{continue_status = {pause, ClientVersions}}, ssl_options = Options0} = State0) -> - try ssl:update_options(NewOptions, ?SERVER_ROLE, Options0) of + try ssl_config:update_options(NewOptions, ?SERVER_ROLE, Options0) of Options = #{versions := Versions} -> State1 = ssl_gen_statem:ssl_config(Options, ?SERVER_ROLE, State0), #state{handshake_env = HsEnv0} = State1, diff --git a/lib/ssl/test/dtls_api_SUITE.erl b/lib/ssl/test/dtls_api_SUITE.erl index aecea455c82..327ac5cee19 100644 --- a/lib/ssl/test/dtls_api_SUITE.erl +++ b/lib/ssl/test/dtls_api_SUITE.erl @@ -438,16 +438,16 @@ client_restarts(Config) -> Msgs = lists:sort(flush()), ReConnect = %% Whitebox re-connect test - fun(#sslsocket{connection_cb = dtls_gen_connection, + fun(#sslsocket{connection_cb = dtls_gen_connection = ConnectionCb, connection_handler = Pid} = Socket, ssl) -> ?CT_LOG("Client Socket: ~p ~n", [Socket]), {ok, IntSocket} = gen_statem:call(Pid, {downgrade, self()}), {{Address,CPort},UDPSocket}=IntSocket, ?CT_LOG("Info: ~p~n", [inet:info(UDPSocket)]), - {ok, #config{transport_info = CbInfo, connection_cb = ConnectionCb, + {ok, #config{transport_info = CbInfo, ssl = SslOpts0}} = - ssl:handle_options(ClientOpts, client, Address), + ssl_config:handle_options(ClientOpts, client, Address), SslOpts = {SslOpts0, #socket_options{}, undefined}, ct:sleep(250), @@ -529,9 +529,9 @@ client_restarts_multiple_acceptors(Config) -> {ok, IntSocket} = gen_statem:call(Pid, {downgrade, self()}), {{Address,CPort},UDPSocket}=IntSocket, ?CT_LOG("Info: ~p~n", [inet:info(UDPSocket)]), - {ok, #config{transport_info = CbInfo, connection_cb = ConnectionCb, + {ok, #config{transport_info = CbInfo, ssl = SslOpts0}} = - ssl:handle_options(ClientOpts, client, Address), + ssl_config:handle_options(ClientOpts, client, Address), SslOpts = {SslOpts0, #socket_options{}, undefined}, ct:sleep(250), ?CT_LOG("Client second connect: ~p ~p~n", [Socket, CbInfo]), diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index 52ac1da97db..8925226bb2b 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -2352,10 +2352,10 @@ customize_defaults(Opts, Role, Host) -> end, case proplists:get_value(protocol, Opts, tls) of dtls -> - {ok, #config{ssl=DOpts}} = ssl:handle_options([{protocol, dtls}|NoVerify], Role, Host), + {ok, #config{ssl=DOpts}} = ssl_config:handle_options([{protocol, dtls}|NoVerify], Role, Host), {DOpts, DefOpts ++ Opts}; tls -> - {ok, #config{ssl=DOpts}} = ssl:handle_options(NoVerify, Role, Host), + {ok, #config{ssl=DOpts}} = ssl_config:handle_options(NoVerify, Role, Host), case proplists:get_value(versions, Opts) of undefined -> {DOpts, DefOpts ++ [{versions, ['tlsv1.2','tlsv1.3']}|Opts]}; @@ -2363,7 +2363,7 @@ customize_defaults(Opts, Role, Host) -> {DOpts, DefOpts ++ Opts} end; _ -> - {ok, #config{ssl=DOpts}} = ssl:handle_options(NoVerify, Role, Host), + {ok, #config{ssl=DOpts}} = ssl_config:handle_options(NoVerify, Role, Host), {DOpts, DefOpts ++ Opts} end. @@ -2372,32 +2372,32 @@ customize_defaults(Opts, Role, Host) -> fun() -> Host = "dummy.host.org", {__DefOpts, __Opts} = customize_defaults(Opts, Role, Host), - try ssl:handle_options(__Opts, Role, Host) of + try ssl_config:handle_options(__Opts, Role, Host) of {ok, #config{ssl=EXP = __ALL}} -> check_expected(ShouldBeMissing, ShouldBeMissing -- maps:keys(__ALL)), - check_expected(__ALL, ssl:update_options([], Role, __ALL)); + check_expected(__ALL, ssl_config:update_options([], Role, __ALL)); Other -> - ?CT_PAL("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), + ?CT_PAL("ssl_config:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), error({unexpected, Other}) catch throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored; C:Other:ST -> - ?CT_PAL("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), + ?CT_PAL("ssl_config:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), error({unexpected, C, Other,ST}) end, - try ssl:update_options(__Opts, Role, __DefOpts) of + try ssl_config:update_options(__Opts, Role, __DefOpts) of EXP = __ALL2 -> check_expected(ShouldBeMissing, ShouldBeMissing -- maps:keys(__ALL2)); Other2 -> - ?CT_PAL("{ok,Cfg} = ssl:handle_options([],~p,~p)," - "ssl:update_options(~w,~w, element(2,Cfg)).", + ?CT_PAL("{ok,Cfg} = ssl_config:handle_options([],~p,~p)," + "ssl_config:update_options(~w,~w, element(2,Cfg)).", [Role,Host,__Opts,Role]), error({unexpected2, Other2}) catch throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored; C2:Other2:ST2 -> - ?CT_PAL("{ok,Cfg} = ssl:handle_options([],~p,~p)," - "ssl:update_options(~p,~p, element(2,Cfg)).", + ?CT_PAL("{ok,Cfg} = ssl_config:handle_options([],~p,~p)," + "ssl_config:update_options(~p,~p, element(2,Cfg)).", [Role,Host,__Opts,Role]), error({unexpected, C2, Other2, ST2}) end @@ -2407,22 +2407,22 @@ customize_defaults(Opts, Role, Host) -> fun() -> Host = "dummy.host.org", {__DefOpts, __Opts} = customize_defaults(Opts, Role, Host), - try ssl:handle_options(__Opts, Role, Host) of + try ssl_config:handle_options(__Opts, Role, Host) of Other -> - ?CT_PAL("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), + ?CT_PAL("ssl_config:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), error({unexpected, Other}) catch throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored; throw:{error, {options, EXP}} -> ok; throw:{error, EXP} -> ok; C:Other:ST -> - ?CT_PAL("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), + ?CT_PAL("ssl_config:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), error({unexpected, C, Other,ST}) end, - try ssl:update_options(__Opts, Role, __DefOpts) of + try ssl_config:update_options(__Opts, Role, __DefOpts) of Other2 -> - ?CT_PAL("{ok,Cfg} = ssl:handle_options([],~p,~p)," - "ssl:update_options(~p,~p, element(2,Cfg)).", + ?CT_PAL("{ok,Cfg} = ssl_config:handle_options([],~p,~p)," + "ssl_config:update_options(~p,~p, element(2,Cfg)).", [Role,Host,__Opts,Role]), error({unexpected, Other2}) catch @@ -2430,8 +2430,8 @@ customize_defaults(Opts, Role, Host) -> throw:{error, {options, EXP}} -> ok; throw:{error, EXP} -> ok; C2:Other2:ST2 -> - ?CT_PAL("{ok,Cfg} = ssl:handle_options([],~p,~p)," - "ssl:update_options(~p,~p, element(2,Cfg)).", + ?CT_PAL("{ok,Cfg} = ssl_config:handle_options([],~p,~p)," + "ssl_config:update_options(~p,~p, element(2,Cfg)).", [Role,Host,__Opts,Role]), error({unexpected, C2, Other2,ST2}) end @@ -2441,22 +2441,22 @@ customize_defaults(Opts, Role, Host) -> fun() -> Host = "dummy.host.org", {__DefOpts, __Opts} = customize_defaults(Opts, Role, Host), - try ssl:handle_options(__Opts, Role, Host) of + try ssl_config:handle_options(__Opts, Role, Host) of {ok, #config{}} -> ok; Other -> - ?CT_PAL("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), + ?CT_PAL("ssl_config:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), error({unexpected, Other}) catch throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored; C:Other:ST -> - ?CT_PAL("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), + ?CT_PAL("ssl_config:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]), error({unexpected, C, Other,ST}) end, - try ssl:update_options(__Opts, Role, __DefOpts) of + try ssl_config:update_options(__Opts, Role, __DefOpts) of Other2 -> - ?CT_PAL("{ok,Cfg} = ssl:handle_options([],~p,~p)," - "ssl:update_options(~p,~p, element(2,Cfg)).", + ?CT_PAL("{ok,Cfg} = ssl_config:handle_options([],~p,~p)," + "ssl_config:update_options(~p,~p, element(2,Cfg)).", [Role,Host,__Opts,Role]), error({unexpected, Other2}) catch @@ -2464,8 +2464,8 @@ customize_defaults(Opts, Role, Host) -> throw:{error, {options, EXP}} -> ok; throw:{error, EXP} -> ok; C2:Other2:ST2 -> - ?CT_PAL("{ok,Cfg} = ssl:handle_options([],~p,~p)," - "ssl:update_options(~p,~p, element(2,Cfg)).", + ?CT_PAL("{ok,Cfg} = ssl_config:handle_options([],~p,~p)," + "ssl_config:update_options(~p,~p, element(2,Cfg)).", [Role,Host,__Opts,Role]), error({unexpected, C2, Other2,ST2}) end @@ -2941,7 +2941,7 @@ options_eccs(_Config) -> options_verify(Config) -> %% fail_if_no_peer_cert, verify, verify_fun, partial_chain Cert = proplists:get_value(cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)), - {ok, #config{ssl = DefOpts = #{verify_fun := {DefVerify,_}}}} = ssl:handle_options([{verify, verify_none}], client, "dummy.host.org"), + {ok, #config{ssl = DefOpts = #{verify_fun := {DefVerify,_}}}} = ssl_config:handle_options([{verify, verify_none}], client, "dummy.host.org"), ?OK(#{fail_if_no_peer_cert := false, verify := verify_none, verify_fun := {DefVerify, []}, partial_chain := _}, [], server), @@ -2971,8 +2971,8 @@ options_verify(Config) -> %% fail_if_no_peer_cert, verify, verify_fun, partial_ %% check verify_fun in update_options case - #{verify_fun := undefined} = ssl:update_options([{verify, verify_peer}, {cacerts, [Cert]}], client, DefOpts), - #{verify_fun := {NewF3, bar}} = ssl:update_options([{verify, verify_peer}, {cacerts, [Cert]}, + #{verify_fun := undefined} = ssl_config:update_options([{verify, verify_peer}, {cacerts, [Cert]}], client, DefOpts), + #{verify_fun := {NewF3, bar}} = ssl_config:update_options([{verify, verify_peer}, {cacerts, [Cert]}, {verify_fun, {NewF3, bar}}], client, DefOpts), diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index bc16a2779a6..c2d2cf7b1c3 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -127,7 +127,7 @@ decode_hello_handshake(_Config) -> 16#70, 16#64, 16#79, 16#2f, 16#32>>, Version = ?SSL_3_0, - DefOpts = ssl:update_options([{verify, verify_none}], client, #{}), + DefOpts = ssl_config:update_options([{verify, verify_none}], client, #{}), {Records, _Buffer} = tls_handshake:get_tls_handshakes(Version, HelloPacket, <<>>, DefOpts), {Hello, _Data} = hd(Records), diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl index 02e65ad49f2..ad002c74a74 100644 --- a/lib/ssl/test/ssl_npn_hello_SUITE.erl +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -169,4 +169,4 @@ create_connection_states() -> }. default_options_map() -> - ssl:update_options([{verify, verify_none}], client, #{}). + ssl_config:update_options([{verify, verify_none}], client, #{}).