From 3e6254f819070eb82c72e94c4469d9baaddfe2f4 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 3 Nov 2023 11:46:14 +0100 Subject: [PATCH] url: proxy ssl connection reuse fix - tunnel https proxy used for http: transfers does no check if proxy-ssl configuration matches - test cases added, test_10_12 fails on 8.4.0 Closes #12255 --- lib/url.c | 20 +++---- tests/http/test_10_proxy.py | 102 ++++++++++++++++++++++++++++++++++++ tests/http/testenv/curl.py | 10 ++-- 3 files changed, 120 insertions(+), 12 deletions(-) diff --git a/lib/url.c b/lib/url.c index 7002b94249..f0d04e8857 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1203,17 +1203,19 @@ ConnectionExists(struct Curl_easy *data, continue; if(IS_HTTPS_PROXY(needle->http_proxy.proxytype)) { - /* use https proxy */ - if(needle->http_proxy.proxytype != - check->http_proxy.proxytype) + /* https proxies come in different types, http/1.1, h2, ... */ + if(needle->http_proxy.proxytype != check->http_proxy.proxytype) continue; - else if(needle->handler->flags&PROTOPT_SSL) { - /* use double layer ssl */ - if(!Curl_ssl_conn_config_match(data, check, TRUE)) - continue; - } - else if(!Curl_ssl_conn_config_match(data, check, FALSE)) + /* match SSL config to proxy */ + if(!Curl_ssl_conn_config_match(data, check, TRUE)) { + DEBUGF(infof(data, + "Connection #%" CURL_FORMAT_CURL_OFF_T + " has different SSL proxy parameters, can't reuse", + check->connection_id)); continue; + } + /* the SSL config to the server, which may apply here is checked + * further below */ } } #endif diff --git a/tests/http/test_10_proxy.py b/tests/http/test_10_proxy.py index df234013c1..0e4060b67c 100644 --- a/tests/http/test_10_proxy.py +++ b/tests/http/test_10_proxy.py @@ -247,3 +247,105 @@ class TestProxy: assert r.total_connects == 2 else: assert r.total_connects == 2 + + @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL") + @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2']) + @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available") + def test_10_10_reuse_proxy(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat): + # url twice via https: proxy separated with '--next', will reuse + if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'): + pytest.skip('only supported with nghttp2') + curl = CurlClient(env=env) + url = f'https://localhost:{env.https_port}/data.json' + proxy_args = curl.get_proxy_args(tunnel=True, proto=tunnel) + r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + extra_args=proxy_args) + r1.check_response(count=1, http_status=200) + assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \ + if tunnel == 'h2' else 'HTTP/1.1' + # get the args, duplicate separated with '--next' + x2_args = r1.args[1:] + x2_args.append('--next') + x2_args.extend(proxy_args) + r2 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + extra_args=x2_args) + r2.check_response(count=2, http_status=200) + assert r2.total_connects == 1 + + @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL") + @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2']) + @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available") + @pytest.mark.skipif(condition=not Env.curl_uses_lib('openssl'), reason="tls13-ciphers not supported") + def test_10_11_noreuse_proxy_https(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat): + # different --proxy-tls13-ciphers, no reuse of connection for https: + curl = CurlClient(env=env) + if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'): + pytest.skip('only supported with nghttp2') + url = f'https://localhost:{env.https_port}/data.json' + proxy_args = curl.get_proxy_args(tunnel=True, proto=tunnel) + r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + extra_args=proxy_args) + r1.check_response(count=1, http_status=200) + assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \ + if tunnel == 'h2' else 'HTTP/1.1' + # get the args, duplicate separated with '--next' + x2_args = r1.args[1:] + x2_args.append('--next') + x2_args.extend(proxy_args) + x2_args.extend(['--proxy-tls13-ciphers', 'TLS_AES_128_GCM_SHA256']) + r2 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + extra_args=x2_args) + r2.check_response(count=2, http_status=200) + assert r2.total_connects == 2 + + @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL") + @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2']) + @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available") + @pytest.mark.skipif(condition=not Env.curl_uses_lib('openssl'), reason="tls13-ciphers not supported") + def test_10_12_noreuse_proxy_http(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat): + # different --proxy-tls13-ciphers, no reuse of connection for http: + if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'): + pytest.skip('only supported with nghttp2') + curl = CurlClient(env=env) + url = f'http://localhost:{env.http_port}/data.json' + proxy_args = curl.get_proxy_args(tunnel=True, proto=tunnel) + r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + extra_args=proxy_args) + r1.check_response(count=1, http_status=200) + assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \ + if tunnel == 'h2' else 'HTTP/1.1' + # get the args, duplicate separated with '--next' + x2_args = r1.args[1:] + x2_args.append('--next') + x2_args.extend(proxy_args) + x2_args.extend(['--proxy-tls13-ciphers', 'TLS_AES_128_GCM_SHA256']) + r2 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + extra_args=x2_args) + r2.check_response(count=2, http_status=200) + assert r2.total_connects == 2 + + @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL") + @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2']) + @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available") + @pytest.mark.skipif(condition=not Env.curl_uses_lib('openssl'), reason="tls13-ciphers not supported") + def test_10_13_noreuse_https(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat): + # different --tls13-ciphers on https: same proxy config + if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'): + pytest.skip('only supported with nghttp2') + curl = CurlClient(env=env) + url = f'https://localhost:{env.https_port}/data.json' + proxy_args = curl.get_proxy_args(tunnel=True, proto=tunnel) + r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + extra_args=proxy_args) + r1.check_response(count=1, http_status=200) + assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \ + if tunnel == 'h2' else 'HTTP/1.1' + # get the args, duplicate separated with '--next' + x2_args = r1.args[1:] + x2_args.append('--next') + x2_args.extend(proxy_args) + x2_args.extend(['--tls13-ciphers', 'TLS_AES_128_GCM_SHA256']) + r2 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + extra_args=x2_args) + r2.check_response(count=2, http_status=200) + assert r2.total_connects == 2 diff --git a/tests/http/testenv/curl.py b/tests/http/testenv/curl.py index ed05b79db2..bc5b4881c7 100644 --- a/tests/http/testenv/curl.py +++ b/tests/http/testenv/curl.py @@ -510,8 +510,14 @@ class CurlClient: args.extend(['--trace-config', 'http/2,http/3,h2-proxy,h1-proxy']) pass + active_options = options + if options is not None and '--next' in options: + active_options = options[options.index('--next') + 1:] + for url in urls: u = urlparse(urls[0]) + if options: + args.extend(options) if alpn_proto is not None: if alpn_proto not in self.ALPN_ARG: raise Exception(f'unknown ALPN protocol: "{alpn_proto}"') @@ -521,7 +527,7 @@ class CurlClient: pass elif insecure: args.append('--insecure') - elif options and "--cacert" in options: + elif active_options and "--cacert" in active_options: pass elif u.hostname: args.extend(["--cacert", self.env.ca.cert_file]) @@ -532,8 +538,6 @@ class CurlClient: args.extend(["--resolve", f"{u.hostname}:{port}:127.0.0.1"]) if timeout is not None and int(timeout) > 0: args.extend(["--connect-timeout", str(int(timeout))]) - if options: - args.extend(options) args.append(url) return args -- 2.47.3