diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/gun_SUITE.erl | 328 | ||||
-rw-r--r-- | test/rfc7540_SUITE.erl | 3 |
2 files changed, 328 insertions, 3 deletions
diff --git a/test/gun_SUITE.erl b/test/gun_SUITE.erl index 766b1e7..7c0440b 100644 --- a/test/gun_SUITE.erl +++ b/test/gun_SUITE.erl @@ -29,10 +29,31 @@ suite() -> [{timetrap, 30000}]. all() -> - [{group, gun}]. + [ + {group, tls13_post_handshake_alert}, + {group, gun}, + {group, tls13_post_handshake_alert} + ]. groups() -> - [{gun, [parallel], ct_helper:all(?MODULE)}]. + [ + {gun, [parallel], ct_helper:all(?MODULE)}, + %% We run these tests in parallel in 'gun' as well as + %% sequentially to have more chances of hitting different + %% parts of the code. We also run them before/after 'gun'. + {tls13_post_handshake_alert, [], [ + tls13_post_handshake_alert_http1, + tls13_post_handshake_alert_http2, + tls13_post_handshake_alert_http1_via_http, + tls13_post_handshake_alert_http2_via_http, + tls13_post_handshake_alert_http1_via_https, + tls13_post_handshake_alert_http2_via_https, + tls13_post_handshake_alert_http1_via_h2c, + tls13_post_handshake_alert_http2_via_h2c, + tls13_post_handshake_alert_http1_via_h2, + tls13_post_handshake_alert_http2_via_h2 + ]} + ]. %% Tests. @@ -633,6 +654,309 @@ supervise_false(_) -> [] = [P || {_, P, _, _} <- supervisor:which_children(gun_sup), P =:= Pid], ok. +tls13_post_handshake_alert_http1(_) -> + case {os:type(), erlang:function_exported(lists, enumerate, 3)} of + {{unix, linux}, true} -> + do_tls13_post_handshake_alert_http1(); + {{unix, linux}, false} -> + {skip, "Handling of TLS 1.3 alerts was improved in an OTP-26 patch release."}; + _ -> + {skip, "This test is only enabled on Linux to avoid intermittent failures."} + end. + +do_tls13_post_handshake_alert_http1() -> + doc("Ensure that a TLS 1.3 post-handshake alert is properly " + "propagated when using HTTP/1.1 in mTLS scenarios."), + TestOpts = ct_helper:get_certs_from_ets(), + {ok, ListenSocket} = ssl:listen(0, [binary, + {log_level, none}, + {versions, ['tlsv1.3']}, + %% The alert will be triggered by the missing certificate. + {verify, verify_peer}, + {fail_if_no_peer_cert, true}, + %% We only use the certs, not other options, + %% as we require TLS 1.3 and want a specific behavior. + {cacerts, proplists:get_value(cacerts, TestOpts)}, + {cert, proplists:get_value(cert, TestOpts)}, + {key, proplists:get_value(key, TestOpts)} + ]), + {ok, {_, OriginPort}} = ssl:sockname(ListenSocket), + _ = spawn_link(fun() -> + {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 5000), + {error, {tls_alert, _}} = ssl:handshake(ClientSocket, 5000), + receive after infinity -> ok end + end), + {ok, ConnPid} = gun:open("localhost", OriginPort, #{ + retry => 0, + transport => tls, + tls_opts => [ + {log_level, none}, + {verify, verify_none} + ] + }), + %% We want to send data immediately as soon as the connection is up. + %% So we do it before we receive the gun_up message. + StreamRef = gun:post(ConnPid, "/", [{<<"content-type">>, <<"application/octet-stream">>}]), + %% Yes, this was written on April 1st. + _ = [begin + gun:data(ConnPid, StreamRef, nofin, <<"April fools!">>) + end || _ <- lists:seq(1, 100)], + %% The alert will occur after the gun_up message has been sent + %% in the case of HTTP/1.1 (when active mode gets enabled). + {ok, http} = gun:await_up(ConnPid), + {error, {down, {shutdown, + {tls_alert, {certificate_required, _}}}}} = gun:await(ConnPid, undefined), + gun:close(ConnPid). + +tls13_post_handshake_alert_http2(_) -> + case {os:type(), erlang:function_exported(lists, enumerate, 3)} of + {{unix, linux}, true} -> + do_tls13_post_handshake_alert_http2(); + {{unix, linux}, false} -> + {skip, "Handling of TLS 1.3 alerts was improved in an OTP-26 patch release."}; + _ -> + {skip, "This test is only enabled on Linux to avoid intermittent failures."} + end. + +do_tls13_post_handshake_alert_http2() -> + doc("Ensure that a TLS 1.3 post-handshake alert is properly " + "propagated when using HTTP/2 in mTLS scenarios."), + TestOpts = ct_helper:get_certs_from_ets(), + {ok, ListenSocket} = ssl:listen(0, [binary, + {log_level, none}, + {versions, ['tlsv1.3']}, + %% Enable HTTP/2. + {alpn_preferred_protocols, [<<"h2">>]}, + %% The alert will be triggered by the missing certificate. + {verify, verify_peer}, + {fail_if_no_peer_cert, true}, + %% We only use the certs, not other options, + %% as we require TLS 1.3 and want a specific behavior. + {cacerts, proplists:get_value(cacerts, TestOpts)}, + {cert, proplists:get_value(cert, TestOpts)}, + {key, proplists:get_value(key, TestOpts)} + ]), + {ok, {_, OriginPort}} = ssl:sockname(ListenSocket), + _ = spawn_link(fun() -> + {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 5000), + {error, {tls_alert, _}} = ssl:handshake(ClientSocket, 5000), + receive after infinity -> ok end + end), + {ok, ConnPid} = gun:open("localhost", OriginPort, #{ + retry => 0, + transport => tls, + tls_opts => [ + {log_level, none}, + {verify, verify_none} + ] + }), + case gun:await_up(ConnPid) of + {error, {down, {shutdown, {tls_alert, {certificate_required, _}}}}} -> + ct:log("TLS alert received in gun:await_up"); + {ok, http2} -> + {error, {down, {shutdown, + {tls_alert, {certificate_required, _}}}}} = gun:await(ConnPid, undefined), + ct:log("TLS alert received after gun:await_up") + end, + gun:close(ConnPid). + +tls13_post_handshake_alert_http1_via_http(_) -> + doc("Ensure that a TLS 1.3 post-handshake alert is properly " + "propagated when connecting to an HTTP/1.1 server " + "over an HTTP/1.1 clear-text tunnel in mTLS scenarios."), + case {os:type(), erlang:function_exported(lists, enumerate, 3)} of + {{unix, linux}, true} -> + do_tls13_post_handshake_alert_via_tunnel(http, http); + {{unix, linux}, false} -> + {skip, "Handling of TLS 1.3 alerts was improved in an OTP-26 patch release."}; + _ -> + {skip, "This test is only enabled on Linux to avoid intermittent failures."} + end. + +tls13_post_handshake_alert_http2_via_http(_) -> + doc("Ensure that a TLS 1.3 post-handshake alert is properly " + "propagated when connecting to an HTTP/2 server " + "over an HTTP/1.1 clear-text tunnel in mTLS scenarios."), + case {os:type(), erlang:function_exported(lists, enumerate, 3)} of + {{unix, linux}, true} -> + do_tls13_post_handshake_alert_via_tunnel(http, http2); + {{unix, linux}, false} -> + {skip, "Handling of TLS 1.3 alerts was improved in an OTP-26 patch release."}; + _ -> + {skip, "This test is only enabled on Linux to avoid intermittent failures."} + end. + +tls13_post_handshake_alert_http1_via_https(_) -> + doc("Ensure that a TLS 1.3 post-handshake alert is properly " + "propagated when connecting to an HTTP/1.1 server " + "over an HTTP/1.1 TLS tunnel in mTLS scenarios."), + case {os:type(), erlang:function_exported(lists, enumerate, 3)} of + {{unix, linux}, true} -> + do_tls13_post_handshake_alert_via_tunnel(https, http); + {{unix, linux}, false} -> + {skip, "Handling of TLS 1.3 alerts was improved in an OTP-26 patch release."}; + _ -> + {skip, "This test is only enabled on Linux to avoid intermittent failures."} + end. + +tls13_post_handshake_alert_http2_via_https(_) -> + doc("Ensure that a TLS 1.3 post-handshake alert is properly " + "propagated when connecting to an HTTP/2 server " + "over an HTTP/1.1 TLS tunnel in mTLS scenarios."), + case {os:type(), erlang:function_exported(lists, enumerate, 3)} of + {{unix, linux}, true} -> + do_tls13_post_handshake_alert_via_tunnel(https, http2); + {{unix, linux}, false} -> + {skip, "Handling of TLS 1.3 alerts was improved in an OTP-26 patch release."}; + _ -> + {skip, "This test is only enabled on Linux to avoid intermittent failures."} + end. + +tls13_post_handshake_alert_http1_via_h2c(_) -> + doc("Ensure that a TLS 1.3 post-handshake alert is properly " + "propagated when connecting to an HTTP/1.1 server " + "over an HTTP/2 clear-text tunnel in mTLS scenarios."), + case {os:type(), erlang:function_exported(lists, enumerate, 3)} of + {{unix, linux}, true} -> + do_tls13_post_handshake_alert_via_tunnel(h2c, http); + {{unix, linux}, false} -> + {skip, "Handling of TLS 1.3 alerts was improved in an OTP-26 patch release."}; + _ -> + {skip, "This test is only enabled on Linux to avoid intermittent failures."} + end. + +tls13_post_handshake_alert_http2_via_h2c(_) -> + doc("Ensure that a TLS 1.3 post-handshake alert is properly " + "propagated when connecting to an HTTP/2 server " + "over an HTTP/2 clear-text tunnel in mTLS scenarios."), + case {os:type(), erlang:function_exported(lists, enumerate, 3)} of + {{unix, linux}, true} -> + do_tls13_post_handshake_alert_via_tunnel(h2c, http2); + {{unix, linux}, false} -> + {skip, "Handling of TLS 1.3 alerts was improved in an OTP-26 patch release."}; + _ -> + {skip, "This test is only enabled on Linux to avoid intermittent failures."} + end. + +tls13_post_handshake_alert_http1_via_h2(_) -> + doc("Ensure that a TLS 1.3 post-handshake alert is properly " + "propagated when connecting to an HTTP/1.1 server " + "over an HTTP/2 TLS tunnel in mTLS scenarios."), + case {os:type(), erlang:function_exported(lists, enumerate, 3)} of + {{unix, linux}, true} -> + do_tls13_post_handshake_alert_via_tunnel(h2, http); + {{unix, linux}, false} -> + {skip, "Handling of TLS 1.3 alerts was improved in an OTP-26 patch release."}; + _ -> + {skip, "This test is only enabled on Linux to avoid intermittent failures."} + end. + +tls13_post_handshake_alert_http2_via_h2(_) -> + doc("Ensure that a TLS 1.3 post-handshake alert is properly " + "propagated when connecting to an HTTP/2 server " + "over an HTTP/2 TLS tunnel in mTLS scenarios."), + case {os:type(), erlang:function_exported(lists, enumerate, 3)} of + {{unix, linux}, true} -> + do_tls13_post_handshake_alert_via_tunnel(h2, http2); + {{unix, linux}, false} -> + {skip, "Handling of TLS 1.3 alerts was improved in an OTP-26 patch release."}; + _ -> + {skip, "This test is only enabled on Linux to avoid intermittent failures."} + end. + +do_tls13_post_handshake_alert_via_tunnel(ProxyType, OriginProtocol) -> + TestOpts = ct_helper:get_certs_from_ets(), + ExtraOpts = case OriginProtocol of + http -> []; + http2 -> [{alpn_preferred_protocols, [<<"h2">>]}] + end, + {ok, ListenSocket} = ssl:listen(0, [binary, + {log_level, none}, + {versions, ['tlsv1.3']}, + %% The alert will be triggered by the missing certificate. + {verify, verify_peer}, + {fail_if_no_peer_cert, true}, + %% We only use the certs, not other options, + %% as we require TLS 1.3 and want a specific behavior. + {cacerts, proplists:get_value(cacerts, TestOpts)}, + {cert, proplists:get_value(cert, TestOpts)}, + {key, proplists:get_value(key, TestOpts)} + |ExtraOpts]), + {ok, {_, OriginPort}} = ssl:sockname(ListenSocket), + _ = spawn_link(fun() -> + {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 5000), + {error, {tls_alert, _}} = ssl:handshake(ClientSocket, 5000), + receive after infinity -> ok end + end), + {ok, ProxyPid, ProxyPort} = tunnel_SUITE:do_proxy_start(ProxyType), + {ProxyTransport, ProxyProtocol} = tunnel_SUITE:do_type(ProxyType), + {ok, ConnPid} = gun:open("localhost", ProxyPort, #{ + retry => 0, + transport => ProxyTransport, + tls_opts => [{verify, verify_none}], + protocols => [ProxyProtocol] + %,trace => true + }), + {ok, ProxyProtocol} = gun:await_up(ConnPid), + tunnel_SUITE:do_handshake_completed(ProxyProtocol, ProxyPid), + TunnelStreamRef = gun:connect(ConnPid, #{ + host => "localhost", + port => OriginPort, + transport => tls, + tls_opts => [ + {log_level, none}, + {verify, verify_none} + ] + }), + {response, fin, 200, _} = gun:await(ConnPid, TunnelStreamRef), + %% When going through an HTTP/2 tunnel we must wait for the protocol + %% to be up before sending data. + Continue = case ProxyProtocol of + http2 -> + case gun:await(ConnPid, TunnelStreamRef) of + {up, OriginProtocol} -> + ok; + {error, {stream_error, {stream_error, + {tls_alert, {certificate_required, _}}, _}}} -> + stop + end; + http -> + ok + end, + case Continue of + stop -> + ok; + ok -> + %% Otherwise we want to send data immediately as soon as the connection + %% is up. So we do it before we receive the gun_up message. + StreamRef = gun:post(ConnPid, "/", [{<<"content-type">>, <<"application/octet-stream">>}], + #{tunnel => TunnelStreamRef}), + %% Yes, this was written on April 1st. + _ = [begin + gun:data(ConnPid, StreamRef, nofin, <<"April fools!">>) + end || _ <- lists:seq(1, 100)], + _ = case ProxyProtocol of + http2 -> + %% We already received the gun_tunnel_up at this point for HTTP/2. + {error, {stream_error, {stream_error, + {tls_alert, {certificate_required, _}}, _}}} = gun:await(ConnPid, TunnelStreamRef); + http -> + %% The alert will occur after the gun_up message has been sent + %% in the case of HTTP/1.1 (when active mode gets enabled). + %% But when we are using TLS over TLS the timing is a little + %% different and we may get a failure before receiving gun_tunnel_up. + case gun:await(ConnPid, TunnelStreamRef) of + {up, OriginProtocol} -> + {error, {down, {shutdown, + {tls_alert, {certificate_required, _}}}}} = gun:await(ConnPid, undefined); + {error, {stream_error, {closed, + {tls_alert, {certificate_required, _}}}}} -> + ok + end + end + end, + gun:close(ConnPid). + tls_handshake_error_gun_http2_init_retry_0(_) -> doc("Ensure an early TLS connection close is propagated " "to the user of the connection."), diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index 847449e..2293d6d 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -110,7 +110,8 @@ do_proxy_parse(<<Len:24, 0:8, _:8, StreamID:32, Payload:Len/binary, Rest/bits>>, ok -> do_proxy_parse(Rest, Proxy); {error, _} -> - ok + %% Wait forever when a connection gets closed. We will exit with the test process. + timer:sleep(infinity) end; do_proxy_parse(<<Len:24, 1:8, _:8, StreamID:32, ReqHeadersBlock:Len/binary, Rest/bits>>, Proxy=#proxy{parent=Parent, socket=Socket, transport=Transport, |