summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Eissing <stefan@eissing.org>2023-02-13 16:15:20 +0100
committerDaniel Stenberg <daniel@haxx.se>2023-02-13 20:54:57 +0100
commitce0cad713dbb97ef3675d1cf6a7b2d358b7643d7 (patch)
treed1f84e6b76f0eccd76f00d722461e3133740066c
parent17153e173d4ff41702963107346853b12ba12d26 (diff)
downloadcurl-ce0cad713dbb97ef3675d1cf6a7b2d358b7643d7.tar.gz
openssl: test and fix for forward proxy handling (non-tunneling).
- adding pytest test_10 cases for proxy httpd setup tests - fixing openssl bug in https: proxy hostname verification that used the hostname of the request and not the proxy name. Closes #10498
-rw-r--r--lib/vtls/openssl.c28
-rw-r--r--tests/tests-httpd/test_10_proxy.py70
-rw-r--r--tests/tests-httpd/testenv/env.py7
-rw-r--r--tests/tests-httpd/testenv/httpd.py36
4 files changed, 133 insertions, 8 deletions
diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c
index 51d2de9e3..971398f4d 100644
--- a/lib/vtls/openssl.c
+++ b/lib/vtls/openssl.c
@@ -2122,6 +2122,22 @@ static bool subj_alt_hostcheck(struct Curl_easy *data,
return FALSE;
}
+static CURLcode
+ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+ X509 *server_cert, const char *hostname,
+ const char *dispname);
+
+CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+ X509 *server_cert)
+{
+ const char *hostname, *dispname;
+ int port;
+
+ (void)conn;
+ Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port);
+ return ossl_verifyhost(data, conn, server_cert, hostname, dispname);
+}
+
/* Quote from RFC2818 section 3.1 "Server Identity"
If a subjectAltName extension of type dNSName is present, that MUST
@@ -2144,8 +2160,10 @@ static bool subj_alt_hostcheck(struct Curl_easy *data,
This function is now used from ngtcp2 (QUIC) as well.
*/
-CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
- X509 *server_cert)
+static CURLcode
+ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+ X509 *server_cert, const char *hostname,
+ const char *dispname)
{
bool matched = FALSE;
int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */
@@ -2159,12 +2177,9 @@ CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
CURLcode result = CURLE_OK;
bool dNSName = FALSE; /* if a dNSName field exists in the cert */
bool iPAddress = FALSE; /* if a iPAddress field exists in the cert */
- const char *hostname, *dispname;
- int port;
size_t hostlen;
(void)conn;
- Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port);
hostlen = strlen(hostname);
#ifndef ENABLE_IPV6
@@ -4129,7 +4144,8 @@ static CURLcode servercert(struct Curl_cfilter *cf,
BIO_free(mem);
if(conn_config->verifyhost) {
- result = Curl_ossl_verifyhost(data, conn, backend->server_cert);
+ result = ossl_verifyhost(data, conn, backend->server_cert,
+ connssl->hostname, connssl->dispname);
if(result) {
X509_free(backend->server_cert);
backend->server_cert = NULL;
diff --git a/tests/tests-httpd/test_10_proxy.py b/tests/tests-httpd/test_10_proxy.py
new file mode 100644
index 000000000..1c444eec0
--- /dev/null
+++ b/tests/tests-httpd/test_10_proxy.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#***************************************************************************
+# _ _ ____ _
+# Project ___| | | | _ \| |
+# / __| | | | |_) | |
+# | (__| |_| | _ <| |___
+# \___|\___/|_| \_\_____|
+#
+# Copyright (C) 2008 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at https://curl.se/docs/copyright.html.
+#
+# You may opt to use, copy, modify, merge, publish, distribute and/or sell
+# copies of the Software, and permit persons to whom the Software is
+# furnished to do so, under the terms of the COPYING file.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+# SPDX-License-Identifier: curl
+#
+###########################################################################
+#
+import logging
+import os
+import pytest
+
+from testenv import Env, CurlClient
+
+
+log = logging.getLogger(__name__)
+
+
+@pytest.mark.skipif(condition=Env.setup_incomplete(),
+ reason=f"missing: {Env.incomplete_reason()}")
+class TestProxy:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env, httpd):
+ push_dir = os.path.join(httpd.docs_dir, 'push')
+ if not os.path.exists(push_dir):
+ os.makedirs(push_dir)
+
+ # download via http: proxy (no tunnel)
+ def test_10_01_http_get(self, env: Env, httpd, repeat):
+ curl = CurlClient(env=env)
+ url = f'http://localhost:{env.http_port}/data.json'
+ r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
+ extra_args=[
+ '--proxy', f'http://{env.proxy_domain}:{env.http_port}/',
+ '--resolve', f'{env.proxy_domain}:{env.http_port}:127.0.0.1',
+ ])
+ assert r.exit_code == 0
+ r.check_stats(count=1, exp_status=200)
+
+ # download via https: proxy (no tunnel)
+ def test_10_02_http_get(self, env: Env, httpd, repeat):
+ curl = CurlClient(env=env)
+ url = f'http://localhost:{env.http_port}/data.json'
+ r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
+ extra_args=[
+ '--proxy', f'https://{env.proxy_domain}:{env.https_port}/',
+ '--resolve', f'{env.proxy_domain}:{env.https_port}:127.0.0.1',
+ '--proxy-cacert', env.ca.cert_file,
+ ])
+ assert r.exit_code == 0
+ r.check_stats(count=1, exp_status=200)
diff --git a/tests/tests-httpd/testenv/env.py b/tests/tests-httpd/testenv/env.py
index 83d3cce4c..07eb999e9 100644
--- a/tests/tests-httpd/testenv/env.py
+++ b/tests/tests-httpd/testenv/env.py
@@ -113,9 +113,11 @@ class EnvConfig:
self.tld = 'tests-httpd.curl.se'
self.domain1 = f"one.{self.tld}"
self.domain2 = f"two.{self.tld}"
+ self.proxy_domain = f"proxy.{self.tld}"
self.cert_specs = [
CertificateSpec(domains=[self.domain1], key_type='rsa2048'),
CertificateSpec(domains=[self.domain2], key_type='rsa2048'),
+ CertificateSpec(domains=[self.proxy_domain], key_type='rsa2048'),
CertificateSpec(name="clientsX", sub_specs=[
CertificateSpec(name="user1", client=True),
]),
@@ -295,6 +297,11 @@ class Env:
return self.CONFIG.domain2
@property
+ def proxy_domain(self) -> str:
+ return self.CONFIG.proxy_domain
+
+
+ @property
def http_port(self) -> str:
return self.CONFIG.http_port
diff --git a/tests/tests-httpd/testenv/httpd.py b/tests/tests-httpd/testenv/httpd.py
index 066ed6db7..24399454a 100644
--- a/tests/tests-httpd/testenv/httpd.py
+++ b/tests/tests-httpd/testenv/httpd.py
@@ -44,9 +44,9 @@ class Httpd:
MODULES = [
'log_config', 'logio', 'unixd', 'version', 'watchdog',
- 'authn_core', 'authz_user', 'authz_core',
+ 'authn_core', 'authz_user', 'authz_core', 'authz_host',
'env', 'filter', 'headers', 'mime',
- 'rewrite', 'http2', 'ssl',
+ 'rewrite', 'http2', 'ssl', 'proxy', 'proxy_http', 'proxy_connect',
'mpm_event',
]
COMMON_MODULES_DIRS = [
@@ -191,6 +191,8 @@ class Httpd:
creds1 = self.env.get_credentials(domain1)
domain2 = self.env.domain2
creds2 = self.env.get_credentials(domain2)
+ proxy_domain = self.env.proxy_domain
+ proxy_creds = self.env.get_credentials(proxy_domain)
self._mkpath(self._conf_dir)
self._mkpath(self._logs_dir)
self._mkpath(self._tmp_dir)
@@ -218,6 +220,8 @@ class Httpd:
f'ErrorLog {self._error_log}',
f'LogLevel {self._get_log_level()}',
f'LogLevel http:trace4',
+ f'LogLevel proxy:trace4',
+ f'LogLevel proxy_http:trace4',
f'H2MinWorkers 16',
f'H2MaxWorkers 128',
f'Listen {self.env.http_port}',
@@ -227,6 +231,7 @@ class Httpd:
conf.extend([ # plain http host for domain1
f'<VirtualHost *:{self.env.http_port}>',
f' ServerName {domain1}',
+ f' ServerAlias localhost',
f' DocumentRoot "{self._docs_dir}"',
])
conf.extend(self._curltest_conf())
@@ -234,6 +239,33 @@ class Httpd:
f'</VirtualHost>',
f'',
])
+ conf.extend([ # http forward proxy
+ f'<VirtualHost *:{self.env.http_port}>',
+ f' ServerName {proxy_domain}',
+ f' Protocols http/1.1',
+ f' ProxyRequests On',
+ f' ProxyVia On',
+ f' AllowCONNECT {self.env.http_port} {self.env.https_port}',
+ f' <Proxy "*">',
+ f' Require ip 127.0.0.1',
+ f' </Proxy>',
+ f'</VirtualHost>',
+ ])
+ conf.extend([ # https forward proxy
+ f'<VirtualHost *:{self.env.https_port}>',
+ f' ServerName {proxy_domain}',
+ f' Protocols http/1.1',
+ f' SSLEngine on',
+ f' SSLCertificateFile {proxy_creds.cert_file}',
+ f' SSLCertificateKeyFile {proxy_creds.pkey_file}',
+ f' ProxyRequests On',
+ f' ProxyVia On',
+ f' AllowCONNECT {self.env.http_port} {self.env.https_port}',
+ f' <Proxy "*">',
+ f' Require ip 127.0.0.1',
+ f' </Proxy>',
+ f'</VirtualHost>',
+ ])
conf.extend([ # https host for domain1, h1 + h2
f'<VirtualHost *:{self.env.https_port}>',
f' ServerName {domain1}',