From 8b5f1609faffcf1166ca54c08df4ca9216c51993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 14 Mar 2024 15:41:30 +0100 Subject: Use public_key:cacerts_get/0 when possible Also "fix" many TLS test failures due to yet more changes in the default options for TLS. Also small changes to make Dialyzer happy. --- Makefile | 3 +- ebin/gun.app | 2 +- src/gun.erl | 80 ++++++++++++++++++++++++++++++++------------------ src/gun_http2.erl | 2 +- src/gun_tunnel.erl | 2 +- test/event_SUITE.erl | 4 ++- test/gun_SUITE.erl | 10 +++++-- test/gun_test.erl | 7 +++-- test/rfc7231_SUITE.erl | 3 +- test/rfc7540_SUITE.erl | 4 ++- test/socks_SUITE.erl | 3 +- 11 files changed, 78 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index cee6ce0..ca63664 100644 --- a/Makefile +++ b/Makefile @@ -7,11 +7,12 @@ PROJECT_VERSION = 2.0.1 # Options. # ERLC_OPTS = -DDEBUG_PROXY=1 +PLT_APPS = crypto runtime_tools CT_OPTS += -ct_hooks gun_ct_hook [] # -boot start_sasl # Dependencies. -LOCAL_DEPS = ssl +LOCAL_DEPS = public_key ssl DEPS = cowlib dep_cowlib = git https://github.com/ninenines/cowlib 2.13.0 diff --git a/ebin/gun.app b/ebin/gun.app index fdc9302..0e1338e 100644 --- a/ebin/gun.app +++ b/ebin/gun.app @@ -3,7 +3,7 @@ {vsn, "2.0.1"}, {modules, ['gun','gun_app','gun_conns_sup','gun_content_handler','gun_cookies','gun_cookies_list','gun_data_h','gun_default_event_h','gun_event','gun_http','gun_http2','gun_pool','gun_pool_events_h','gun_pools_sup','gun_protocols','gun_public_suffix','gun_raw','gun_socks','gun_sse_h','gun_sup','gun_tcp','gun_tcp_proxy','gun_tls','gun_tls_proxy','gun_tls_proxy_cb','gun_tls_proxy_http2_connect','gun_tunnel','gun_ws','gun_ws_h','gun_ws_protocol']}, {registered, [gun_sup]}, - {applications, [kernel,stdlib,ssl,cowlib]}, + {applications, [kernel,stdlib,public_key,ssl,cowlib]}, {optional_applications, []}, {mod, {gun_app, []}}, {env, []} diff --git a/src/gun.erl b/src/gun.erl index 31833f5..b4c1686 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -103,7 +103,7 @@ -export([domain_lookup/3]). -export([connecting/3]). -export([initial_tls_handshake/3]). --export([ensure_alpn_sni/3]). +-export([ensure_tls_opts/3]). -export([tls_handshake/3]). -export([connected_protocol_init/3]). -export([connected/3]). @@ -281,6 +281,26 @@ }. -export_type([ws_opts/0]). +-type resp_headers() :: [{binary(), binary()}]. + +-type await_result() :: {inform, 100..199, resp_headers()} + | {response, fin | nofin, non_neg_integer(), resp_headers()} + | {data, fin | nofin, binary()} + | {sse, cow_sse:event() | fin} + | {trailers, resp_headers()} + | {push, stream_ref(), binary(), binary(), resp_headers()} + | {upgrade, [binary()], resp_headers()} + | {ws, ws_frame()} + | {up, http | http2 | raw | socks} + | {notify, settings_changed, map()} + | {error, {stream_error | connection_error | down, any()} | timeout}. +-export_type([await_result/0]). + +-type await_body_result() :: {ok, binary()} + | {ok, binary(), resp_headers()} + | {error, {stream_error | connection_error | down, any()} | timeout}. +-export_type([await_body_result/0]). + -record(state, { owner :: pid(), status :: {up, reference()} | {down, any()} | shutdown, @@ -445,12 +465,12 @@ check_protocols_opt(Protocols) -> consider_tracing(ServerPid, #{trace := true}) -> dbg:tracer(), - dbg:tpl(gun, [{'_', [], [{return_trace}]}]), - dbg:tpl(gun_http, [{'_', [], [{return_trace}]}]), - dbg:tpl(gun_http2, [{'_', [], [{return_trace}]}]), - dbg:tpl(gun_raw, [{'_', [], [{return_trace}]}]), - dbg:tpl(gun_socks, [{'_', [], [{return_trace}]}]), - dbg:tpl(gun_ws, [{'_', [], [{return_trace}]}]), + _ = dbg:tpl(gun, [{'_', [], [{return_trace}]}]), + _ = dbg:tpl(gun_http, [{'_', [], [{return_trace}]}]), + _ = dbg:tpl(gun_http2, [{'_', [], [{return_trace}]}]), + _ = dbg:tpl(gun_raw, [{'_', [], [{return_trace}]}]), + _ = dbg:tpl(gun_socks, [{'_', [], [{return_trace}]}]), + _ = dbg:tpl(gun_ws, [{'_', [], [{return_trace}]}]), dbg:p(ServerPid, all); consider_tracing(_, _) -> ok. @@ -707,19 +727,6 @@ connect(ServerPid, Destination, Headers, ReqOpts) -> %% Awaiting gun messages. --type resp_headers() :: [{binary(), binary()}]. --type await_result() :: {inform, 100..199, resp_headers()} - | {response, fin | nofin, non_neg_integer(), resp_headers()} - | {data, fin | nofin, binary()} - | {sse, cow_sse:event() | fin} - | {trailers, resp_headers()} - | {push, stream_ref(), binary(), binary(), resp_headers()} - | {upgrade, [binary()], resp_headers()} - | {ws, ws_frame()} - | {up, http | http2 | raw | socks} - | {notify, settings_changed, map()} - | {error, {stream_error | connection_error | down, any()} | timeout}. - -spec await(pid(), stream_ref()) -> await_result(). await(ServerPid, StreamRef) -> MRef = monitor(process, ServerPid), @@ -769,10 +776,6 @@ await(ServerPid, StreamRef, Timeout, MRef) -> {error, timeout} end. --type await_body_result() :: {ok, binary()} - | {ok, binary(), resp_headers()} - | {error, {stream_error | connection_error | down, any()} | timeout}. - -spec await_body(pid(), stream_ref()) -> await_body_result(). await_body(ServerPid, StreamRef) -> MRef = monitor(process, ServerPid), @@ -1097,7 +1100,7 @@ connecting(_, {retries, Retries, LookupInfo}, State=#state{opts=Opts, initial_tls_handshake(_, {retries, Retries, Socket}, State0=#state{opts=Opts, origin_host=OriginHost}) -> Protocols = maps:get(protocols, Opts, [http2, http]), HandshakeEvent = #{ - tls_opts => ensure_alpn_sni(Protocols, maps:get(tls_opts, Opts, []), OriginHost), + tls_opts => ensure_tls_opts(Protocols, maps:get(tls_opts, Opts, []), OriginHost), timeout => maps:get(tls_handshake_timeout, Opts, infinity) }, case normal_tls_handshake(Socket, State0, HandshakeEvent, Protocols) of @@ -1109,7 +1112,26 @@ initial_tls_handshake(_, {retries, Retries, Socket}, State0=#state{opts=Opts, or {next_event, internal, {retries, Retries, Reason}}} end. -ensure_alpn_sni(Protocols0, TransOpts0, OriginHost) -> +ensure_tls_opts(Protocols0, TransOpts0, OriginHost) -> + %% CA certificates. + TransOpts1 = case lists:keymember(cacerts, 1, TransOpts0) of + true -> + TransOpts0; + false -> + case lists:keymember(cacertfile, 1, TransOpts0) of + true -> + TransOpts0; + false -> + %% This function was added in OTP-25. We use it when it is + %% available and keep the previous behavior when it isn't. + case erlang:function_exported(public_key, cacerts_get, 0) of + true -> + [{cacerts, public_key:cacerts_get()}|TransOpts0]; + false -> + TransOpts0 + end + end + end, %% ALPN. Protocols = lists:foldl(fun (http, Acc) -> [<<"http/1.1">>|Acc]; @@ -1120,7 +1142,7 @@ ensure_alpn_sni(Protocols0, TransOpts0, OriginHost) -> end, [], Protocols0), TransOpts = [ {alpn_advertised_protocols, Protocols} - |TransOpts0], + |TransOpts1], %% SNI. %% %% Normally only DNS hostnames are supported for SNI. However, the ssl @@ -1161,7 +1183,7 @@ tls_handshake(internal, {tls_handshake, HandshakeEvent0=#{tls_opts := TLSOpts0, timeout := TLSTimeout}, Protocols, ReplyTo}, State=#state{socket=Socket, transport=Transport, origin_host=OriginHost, origin_port=OriginPort, event_handler=EvHandler, event_handler_state=EvHandlerState0}) -> - TLSOpts = ensure_alpn_sni(Protocols, TLSOpts0, OriginHost), + TLSOpts = ensure_tls_opts(Protocols, TLSOpts0, OriginHost), HandshakeEvent = HandshakeEvent0#{ tls_opts => TLSOpts, socket => Socket @@ -1201,7 +1223,7 @@ tls_handshake(Type, Event, State) -> normal_tls_handshake(Socket, State=#state{ origin_host=OriginHost, event_handler=EvHandler, event_handler_state=EvHandlerState0}, HandshakeEvent0=#{tls_opts := TLSOpts0, timeout := TLSTimeout}, Protocols) -> - TLSOpts = ensure_alpn_sni(Protocols, TLSOpts0, OriginHost), + TLSOpts = ensure_tls_opts(Protocols, TLSOpts0, OriginHost), HandshakeEvent = HandshakeEvent0#{ tls_opts => TLSOpts, socket => Socket diff --git a/src/gun_http2.erl b/src/gun_http2.erl index 71e01d4..bfd2d31 100644 --- a/src/gun_http2.erl +++ b/src/gun_http2.erl @@ -585,7 +585,7 @@ headers_frame_connect(State=#http2_state{transport=Transport, opts=Opts, tunnel_ ProtoOpts = case Destination of #{transport := tls} -> Protocols = maps:get(protocols, Destination, [http2, http]), - TLSOpts = gun:ensure_alpn_sni(Protocols, maps:get(tls_opts, Destination, []), DestHost), + TLSOpts = gun:ensure_tls_opts(Protocols, maps:get(tls_opts, Destination, []), DestHost), HandshakeEvent = #{ stream_ref => RealStreamRef, reply_to => ReplyTo, diff --git a/src/gun_tunnel.erl b/src/gun_tunnel.erl index c7fecc7..789d1e3 100644 --- a/src/gun_tunnel.erl +++ b/src/gun_tunnel.erl @@ -565,7 +565,7 @@ commands([{tls_handshake, HandshakeEvent0, Protocols, ReplyTo}|Tail], stream_ref := StreamRef, tls_opts := TLSOpts0 } = HandshakeEvent0, - TLSOpts = gun:ensure_alpn_sni(Protocols, TLSOpts0, OriginHost), + TLSOpts = gun:ensure_tls_opts(Protocols, TLSOpts0, OriginHost), HandshakeEvent = HandshakeEvent0#{ tls_opts => TLSOpts }, diff --git a/test/event_SUITE.erl b/test/event_SUITE.erl index e7def6e..81cdf09 100644 --- a/test/event_SUITE.erl +++ b/test/event_SUITE.erl @@ -55,7 +55,9 @@ init_per_suite(Config) -> }, {ok, _} = cowboy:start_clear({?MODULE, tcp}, [], ProtoOpts), TCPOriginPort = ranch:get_port({?MODULE, tcp}), - {ok, _} = cowboy:start_tls({?MODULE, tls}, ct_helper:get_certs_from_ets(), ProtoOpts), + {ok, _} = cowboy:start_tls({?MODULE, tls}, + [{fail_if_no_peer_cert, false}|ct_helper:get_certs_from_ets()], + ProtoOpts), TLSOriginPort = ranch:get_port({?MODULE, tls}), [{tcp_origin_port, TCPOriginPort}, {tls_origin_port, TLSOriginPort}|Config]. diff --git a/test/gun_SUITE.erl b/test/gun_SUITE.erl index 656158e..8b90774 100644 --- a/test/gun_SUITE.erl +++ b/test/gun_SUITE.erl @@ -462,13 +462,15 @@ server_name_indication_custom(_) -> do_server_name_indication("localhost", net_adm:localhost(), #{ tls_opts => [ {verify, verify_none}, {versions, ['tlsv1.2']}, + {fail_if_no_peer_cert, false}, {server_name_indication, net_adm:localhost()}] }). server_name_indication_default(_) -> doc("Ensure a default server_name_indication is accepted."), do_server_name_indication(net_adm:localhost(), net_adm:localhost(), #{ - tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}] + tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}, + {fail_if_no_peer_cert, false}] }). do_server_name_indication(Host, Expected, GunOpts) -> @@ -630,7 +632,8 @@ tls_handshake_error_gun_http2_init_retry_0(_) -> }}, protocols => [http2], retry => 0, - transport => tls + transport => tls, + tls_opts => [{verify, verify_none}] }), {error, {down, {shutdown, closed}}} = gun:await_up(ConnPid), gun:close(ConnPid). @@ -665,7 +668,8 @@ tls_handshake_error_gun_http2_init_retry_1(_) -> }}, protocols => [http2], retry => 1, - transport => tls + transport => tls, + tls_opts => [{verify, verify_none}] }), {error, {down, {shutdown, closed}}} = gun:await_up(ConnPid), gun:close(ConnPid). diff --git a/test/gun_test.erl b/test/gun_test.erl index 18fcfbf..cffeed5 100644 --- a/test/gun_test.erl +++ b/test/gun_test.erl @@ -24,7 +24,9 @@ init_cowboy_tcp(Ref, ProtoOpts, Config) -> init_cowboy_tls(Ref, ProtoOpts, Config) -> Opts = ct_helper:get_certs_from_ets(), - {ok, _} = cowboy:start_tls(Ref, Opts ++ [{port, 0}], ProtoOpts), + {ok, _} = cowboy:start_tls(Ref, + [{verify, verify_none}, {fail_if_no_peer_cert, false}] + ++ Opts ++ [{port, 0}], ProtoOpts), [{ref, Ref}, {port, ranch:get_port(Ref)}|Config]. %% Origin server helpers. @@ -64,7 +66,8 @@ init_origin(Parent, tls, Protocol, Fun) -> end, %% sni_hosts is necessary for SNI tests to succeed. Opts = [{sni_hosts, [{net_adm:localhost(), []}]}|Opts1], - {ok, ListenSocket} = ssl:listen(0, [binary, {active, false}|Opts]), + {ok, ListenSocket} = ssl:listen(0, [binary, {active, false}, + {fail_if_no_peer_cert, false}|Opts]), {ok, {_, Port}} = ssl:sockname(ListenSocket), Parent ! {self(), Port}, {ok, ClientSocket0} = ssl:transport_accept(ListenSocket, 5000), diff --git a/test/rfc7231_SUITE.erl b/test/rfc7231_SUITE.erl index a5e1fe5..f3a780e 100644 --- a/test/rfc7231_SUITE.erl +++ b/test/rfc7231_SUITE.erl @@ -55,7 +55,8 @@ do_proxy_init(Parent, Transport, Status, ConnectRespHeaders, Delay, ConnectRespV gen_tcp:listen(0, [binary, {active, false}]); gun_tls -> Opts = ct_helper:get_certs_from_ets(), - ssl:listen(0, [binary, {active, false}|Opts]) + ssl:listen(0, [binary, {active, false}, {verify, verify_none}, + {fail_if_no_peer_cert, false}|Opts]) end, {ok, {_, Port}} = Transport:sockname(ListenSocket), Parent ! {self(), Port}, diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index ac88469..79ae347 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -62,7 +62,9 @@ do_proxy_init(Proxy=#proxy{parent=Parent, transport=Transport}) -> gen_tcp:listen(0, [binary, {active, false}]); gun_tls -> Opts = ct_helper:get_certs_from_ets(), - ssl:listen(0, [binary, {active, false}, {alpn_preferred_protocols, [<<"h2">>]}|Opts]) + ssl:listen(0, [binary, {active, false}, {verify, verify_none}, + {fail_if_no_peer_cert, false}, + {alpn_preferred_protocols, [<<"h2">>]}|Opts]) end, {ok, {_, Port}} = Transport:sockname(ListenSocket), Parent ! {self(), Port}, diff --git a/test/socks_SUITE.erl b/test/socks_SUITE.erl index bd88cbb..19d15ca 100644 --- a/test/socks_SUITE.erl +++ b/test/socks_SUITE.erl @@ -50,7 +50,8 @@ do_proxy_init(Parent, Transport, Auth) -> gen_tcp:listen(0, [binary, {active, false}]); gun_tls -> Opts = ct_helper:get_certs_from_ets(), - ssl:listen(0, [binary, {active, false}|Opts]) + ssl:listen(0, [binary, {active, false}, {verify, verify_none}, + {fail_if_no_peer_cert, false}|Opts]) end, {ok, {_, Port}} = Transport:sockname(ListenSocket), Parent ! {self(), Port}, -- cgit v1.2.3