]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
openssl: test and fix for forward proxy handling (non-tunneling).
authorStefan Eissing <stefan@eissing.org>
Mon, 13 Feb 2023 15:15:20 +0000 (16:15 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 13 Feb 2023 19:54:57 +0000 (20:54 +0100)
- 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

lib/vtls/openssl.c
tests/tests-httpd/test_10_proxy.py [new file with mode: 0644]
tests/tests-httpd/testenv/env.py
tests/tests-httpd/testenv/httpd.py

index 51d2de9e3dfcf80451a8d3bec39d2371aeaddec3..971398f4df64098926cc89fb275ea8966a821624 100644 (file)
@@ -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 (file)
index 0000000..1c444ee
--- /dev/null
@@ -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)
index 83d3cce4c7a669c42480c2fd9368cd1730180b4b..07eb999e9e47ed3d535f46021917bf69e5fd5ccd 100644 (file)
@@ -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),
             ]),
@@ -294,6 +296,11 @@ class Env:
     def domain2(self) -> str:
         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
index 066ed6db7027ab845e01d6b5cc58b7d46e645328..24399454a50c68aac31d91d27c7d688d1bb23336 100644 (file)
@@ -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}',