From 05921cf78a132a89e00700280e9a33529a9c6d91 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 24 Dec 2024 07:56:11 +0100 Subject: [PATCH 1/3] Enable just_tls specs from mongoose_tls callback behaviours --- src/just_tls.erl | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/just_tls.erl b/src/just_tls.erl index 68c833a7e21..3e84a74053c 100644 --- a/src/just_tls.erl +++ b/src/just_tls.erl @@ -61,42 +61,36 @@ tcp_to_tls(TCPSocket, Options) -> _ -> Ret end. -%% -callback send(tls_socket(), binary()) -> ok | {error, any()}. +-spec send(tls_socket(), binary()) -> ok | {error, any()}. send(#tls_socket{ssl_socket = SSLSocket}, Packet) -> ssl:send(SSLSocket, Packet). -%% -callback recv_data(tls_socket(), binary()) -> {ok, binary()} | {error, any()}. -recv_data(_, <<"">>) -> +-spec recv_data(tls_socket(), binary()) -> {ok, binary()} | {error, any()}. +recv_data(_, <<>>) -> %% such call is required for fast_tls to accomplish %% tls handshake, for just_tls we can ignore it - {ok, <<"">>}; + {ok, <<>>}; recv_data(#tls_socket{ssl_socket = SSLSocket}, Data1) -> case ssl:recv(SSLSocket, 0, 0) of {ok, Data2} -> {ok, <>}; _ -> {ok, Data1} end. -%% -callback controlling_process(tls_socket(), pid()) -> ok | {error, any()}. +-spec controlling_process(tls_socket(), pid()) -> ok | {error, any()}. controlling_process(#tls_socket{ssl_socket = SSLSocket}, Pid) -> ssl:controlling_process(SSLSocket, Pid). - -%% -callback sockname(tls_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | -%% {error, any()}. +-spec sockname(tls_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, any()}. sockname(#tls_socket{ssl_socket = SSLSocket}) -> ssl:sockname(SSLSocket). - -%% -callback peername(tls_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | -%% {error, any()}. +-spec peername(tls_socket()) -> + {ok, {inet:ip_address(), inet:port_number()}} | {error, any()}. peername(#tls_socket{ssl_socket = SSLSocket}) -> ssl:peername(SSLSocket). - -%% -callback setopts(tls_socket(), Opts::list()) -> ok | {error, any()}. +-spec setopts(tls_socket(), Opts::list()) -> ok | {error, any()}. setopts(#tls_socket{ssl_socket = SSLSocket}, Opts) -> ssl:setopts(SSLSocket, Opts). - -%% -callback get_peer_certificate(tls_socket()) -> {ok, Cert::any()} | -%% {bad_cert, bitstring()} | -%% no_peer_cert. +-spec get_peer_certificate(tls_socket()) -> + {ok, Cert::any()} | {bad_cert, bitstring()} | no_peer_cert. get_peer_certificate(#tls_socket{verify_results = [], ssl_socket = SSLSocket}) -> case ssl:peercert(SSLSocket) of {ok, PeerCert} -> @@ -107,8 +101,9 @@ get_peer_certificate(#tls_socket{verify_results = [], ssl_socket = SSLSocket}) - get_peer_certificate(#tls_socket{verify_results = [Err | _]}) -> {bad_cert, error_to_list(Err)}. -%% -callback close(tls_socket()) -> ok. -close(#tls_socket{ssl_socket = SSLSocket}) -> ssl:close(SSLSocket). +-spec close(tls_socket()) -> ok. +close(#tls_socket{ssl_socket = SSLSocket}) -> + ssl:close(SSLSocket). %% @doc Prepare SSL options for direct use of ssl:connect/2 (client side) %% The `disconnect_on_failure' option is expected to be unset or true From 7c5266397b01ef94d29b2532f586b6d0170d6ce2 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 24 Dec 2024 07:27:16 +0100 Subject: [PATCH 2/3] Optimise just_tls with hibernate_after Note that this can reduce memory consumption by a factor of 4x. Also clean initial calculations of the SSL options, to generate less intermediate garbage when creating the proplist. --- src/c2s/mongoose_c2s_ranch.erl | 11 ++--- src/just_tls.erl | 77 ++++++++++++++++++---------------- src/mongoose_tls.erl | 1 + 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/c2s/mongoose_c2s_ranch.erl b/src/c2s/mongoose_c2s_ranch.erl index cceba5b7c18..fa9058cc03e 100644 --- a/src/c2s/mongoose_c2s_ranch.erl +++ b/src/c2s/mongoose_c2s_ranch.erl @@ -48,15 +48,16 @@ socket_peername(#state{ip = Ip}) -> -spec tcp_to_tls(state(), mongoose_listener:options()) -> {ok, state()} | {error, term()}. -tcp_to_tls(#state{socket = TcpSocket} = State, #{tls := #{module := TlsMod} = TlsConfig}) -> - case tcp_to_tls(TlsMod, TcpSocket, TlsConfig) of +tcp_to_tls(#state{socket = TcpSocket} = State, + #{hibernate_after := HibernateAfter, tls := #{module := TlsMod} = TlsConfig}) -> + case tcp_to_tls(TlsMod, TcpSocket, TlsConfig, HibernateAfter) of {ok, TlsSocket} -> {ok, State#state{transport = TlsMod, socket = TlsSocket}}; {error, Reason} -> {error, Reason} end. -tcp_to_tls(fast_tls, TcpSocket, TlsConfig) -> +tcp_to_tls(fast_tls, TcpSocket, TlsConfig, _HibernateAfter) -> PreparedOpts = mongoose_tls:prepare_options(fast_tls, maps:remove(module, TlsConfig)), ranch_tcp:setopts(TcpSocket, [{active, false}]), case fast_tls:tcp_to_tls(TcpSocket, PreparedOpts) of @@ -65,8 +66,8 @@ tcp_to_tls(fast_tls, TcpSocket, TlsConfig) -> {ok, TlsSocket}; Other -> Other end; -tcp_to_tls(just_tls, TcpSocket, TlsConfig) -> - case just_tls:tcp_to_tls(TcpSocket, TlsConfig) of +tcp_to_tls(just_tls, TcpSocket, TlsConfig, HibernateAfter) -> + case just_tls:tcp_to_tls(TcpSocket, TlsConfig#{hibernate_after => HibernateAfter}) of {ok, TlsSocket} -> {ok, TlsSocket}; Other -> Other end. diff --git a/src/just_tls.erl b/src/just_tls.erl index 3e84a74053c..9920de5fe17 100644 --- a/src/just_tls.erl +++ b/src/just_tls.erl @@ -50,8 +50,7 @@ tcp_to_tls(TCPSocket, Options) -> {Ref, SSLOpts} = format_opts_with_ref(Options, false), {Ref, ssl:connect(TCPSocket, SSLOpts)}; #{} -> - FailIfNoPeerCert = fail_if_no_peer_cert_opt(Options), - {Ref, SSLOpts} = format_opts_with_ref(Options, FailIfNoPeerCert), + {Ref, SSLOpts} = format_opts_with_ref(Options, fail_if_no_peer_cert), {Ref, ssl:handshake(TCPSocket, SSLOpts, 5000)} end, VerifyResults = receive_verify_results(Ref1), @@ -116,8 +115,7 @@ make_ssl_opts(Opts) -> %% The `disconnect_on_failure' option is expected to be unset or true -spec make_cowboy_ssl_opts(mongoose_tls:options()) -> [ssl:tls_option()]. make_cowboy_ssl_opts(Opts) -> - FailIfNoPeerCert = fail_if_no_peer_cert_opt(Opts), - {dummy_ref, SSLOpts} = format_opts_with_ref(Opts, FailIfNoPeerCert), + {dummy_ref, SSLOpts} = format_opts_with_ref(Opts, fail_if_no_peer_cert), SSLOpts. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -125,43 +123,48 @@ make_cowboy_ssl_opts(Opts) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% format_opts_with_ref(Opts, FailIfNoPeerCert) -> - Verify = verify_opt(Opts), {Ref, VerifyFun} = verify_fun_opt(Opts), - SNIOpts = sni_opts(Opts), - SSLOpts = maps:to_list(maps:with(ssl_option_keys(), Opts)), - {Ref, [{fail_if_no_peer_cert, FailIfNoPeerCert}, {verify, Verify}, {verify_fun, VerifyFun}] ++ - SNIOpts ++ SSLOpts}. + SslOpts0 = maps:to_list(maps:with(ssl_option_keys(), Opts)), + SslOpts1 = sni_opts(SslOpts0, Opts), + SslOpts2 = verify_opts(SslOpts1, Opts), + SslOpts3 = hibernate_opts(SslOpts2, Opts), + SslOpts4 = fail_if_no_peer_cert_opts(SslOpts3, Opts, FailIfNoPeerCert), + SslOpts = [{verify_fun, VerifyFun} | SslOpts4], + {Ref, SslOpts}. ssl_option_keys() -> [certfile, cacertfile, ciphers, keyfile, password, versions, dhfile]. -sni_opts(#{server_name_indication := SNIOpts}) -> - process_sni_opts(SNIOpts); -sni_opts(#{}) -> - []. - -process_sni_opts(#{enabled := false}) -> - [{server_name_indication, disable}]; -process_sni_opts(#{enabled := true, host := SNIHost, protocol := https}) -> - [{server_name_indication, SNIHost}, - {customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}]; -process_sni_opts(#{enabled := true, host := SNIHost, protocol := default}) -> - [{server_name_indication, SNIHost}]; -process_sni_opts(#{enabled := true}) -> - []. - -error_to_list(_Error) -> - %TODO: implement later if needed - "verify_fun failed". - -verify_opt(#{verify_mode := none}) -> verify_none; -verify_opt(#{}) -> verify_peer. - %% accept empty peer certificate if explicitly requested not to fail -fail_if_no_peer_cert_opt(#{disconnect_on_failure := false}) -> false; -fail_if_no_peer_cert_opt(#{verify_mode := peer}) -> true; -fail_if_no_peer_cert_opt(#{verify_mode := selfsigned_peer}) -> true; -fail_if_no_peer_cert_opt(#{}) -> false. +fail_if_no_peer_cert_opts(Opts, #{}, false) -> + [{fail_if_no_peer_cert, false} | Opts]; +fail_if_no_peer_cert_opts(Opts, #{disconnect_on_failure := false}, _) -> + [{fail_if_no_peer_cert, false} | Opts]; +fail_if_no_peer_cert_opts(Opts, #{verify_mode := Mode}, _) + when Mode =:= peer; Mode =:= selfsigned_peer -> + [{fail_if_no_peer_cert, true} | Opts]; +fail_if_no_peer_cert_opts(Opts, #{}, _) -> + [{fail_if_no_peer_cert, false} | Opts]. + +hibernate_opts(Opts, #{hibernate_after := Timeout}) -> + [{hibernate_after, Timeout} | Opts]; +hibernate_opts(Opts, #{}) -> + Opts. + +verify_opts(Opts, #{verify_mode := none}) -> + [{verify, verify_none} | Opts]; +verify_opts(Opts, #{}) -> + [{verify, verify_peer} | Opts]. + +sni_opts(Opts, #{server_name_indication := #{enabled := false}}) -> + [{server_name_indication, disable} | Opts]; +sni_opts(Opts, #{server_name_indication := #{enabled := true, host := SNIHost, protocol := default}}) -> + [{server_name_indication, SNIHost} | Opts]; +sni_opts(Opts, #{server_name_indication := #{enabled := true, host := SNIHost, protocol := https}}) -> + [{server_name_indication, SNIHost}, + {customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]} | Opts]; +sni_opts(Opts, #{}) -> + Opts. %% This function translates TLS options to the function %% which will later be used when TCP socket is upgraded to TLS @@ -231,3 +234,7 @@ receive_verify_results(Ref, Acc) -> after 0 -> lists:reverse(Acc) end. + +error_to_list(_Error) -> + %TODO: implement later if needed + "verify_fun failed". diff --git a/src/mongoose_tls.erl b/src/mongoose_tls.erl index 1ddbf68565e..b31961b1c48 100644 --- a/src/mongoose_tls.erl +++ b/src/mongoose_tls.erl @@ -51,6 +51,7 @@ password => string(), versions => [atom()], server_name_indication => sni_options(), % client-only + hibernate_after => timeout(), % only for fast_tls protocol_options => [string()]}. From 80e5e9452afab1542e1893a4cf5ed6659904baca Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 24 Dec 2024 08:02:43 +0100 Subject: [PATCH 3/3] Improve readability of the verify_fun helpers --- src/just_tls.erl | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/just_tls.erl b/src/just_tls.erl index 9920de5fe17..368fa3ff872 100644 --- a/src/just_tls.erl +++ b/src/just_tls.erl @@ -107,30 +107,33 @@ close(#tls_socket{ssl_socket = SSLSocket}) -> %% @doc Prepare SSL options for direct use of ssl:connect/2 (client side) %% The `disconnect_on_failure' option is expected to be unset or true -spec make_ssl_opts(mongoose_tls:options()) -> [ssl:tls_option()]. -make_ssl_opts(Opts) -> - {dummy_ref, SSLOpts} = format_opts_with_ref(Opts, false), - SSLOpts. +make_ssl_opts(#{verify_mode := Mode} = Opts) -> + SslOpts = format_opts(Opts, false), + [{verify_fun, verify_fun(Mode)} | SslOpts]. %% @doc Prepare SSL options for direct use of ssl:handshake/2 (server side) %% The `disconnect_on_failure' option is expected to be unset or true -spec make_cowboy_ssl_opts(mongoose_tls:options()) -> [ssl:tls_option()]. -make_cowboy_ssl_opts(Opts) -> - {dummy_ref, SSLOpts} = format_opts_with_ref(Opts, fail_if_no_peer_cert), - SSLOpts. +make_cowboy_ssl_opts(#{verify_mode := Mode} = Opts) -> + SslOpts = format_opts(Opts, fail_if_no_peer_cert), + [{verify_fun, verify_fun(Mode)} | SslOpts]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% local functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% format_opts_with_ref(Opts, FailIfNoPeerCert) -> + SslOpts0 = format_opts(Opts, FailIfNoPeerCert), {Ref, VerifyFun} = verify_fun_opt(Opts), + SslOpts = [{verify_fun, VerifyFun} | SslOpts0], + {Ref, SslOpts}. + +format_opts(Opts, FailIfNoPeerCert) -> SslOpts0 = maps:to_list(maps:with(ssl_option_keys(), Opts)), SslOpts1 = sni_opts(SslOpts0, Opts), SslOpts2 = verify_opts(SslOpts1, Opts), SslOpts3 = hibernate_opts(SslOpts2, Opts), - SslOpts4 = fail_if_no_peer_cert_opts(SslOpts3, Opts, FailIfNoPeerCert), - SslOpts = [{verify_fun, VerifyFun} | SslOpts4], - {Ref, SslOpts}. + fail_if_no_peer_cert_opts(SslOpts3, Opts, FailIfNoPeerCert). ssl_option_keys() -> [certfile, cacertfile, ciphers, keyfile, password, versions, dhfile]. @@ -224,8 +227,10 @@ verify_fun(none) -> send_verification_failure(Pid, Ref, Reason) -> Pid ! {cert_verification_failure, Ref, Reason}. -receive_verify_results(dummy_ref) -> []; -receive_verify_results(Ref) -> receive_verify_results(Ref, []). +receive_verify_results(dummy_ref) -> + []; +receive_verify_results(Ref) -> + receive_verify_results(Ref, []). receive_verify_results(Ref, Acc) -> receive