mirror of
https://github.com/curl/curl.git
synced 2026-07-16 12:47:16 +03:00
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
This commit is contained in:
parent
17153e173d
commit
ce0cad713d
4 changed files with 133 additions and 8 deletions
|
|
@ -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;
|
||||
|
|
|
|||
70
tests/tests-httpd/test_10_proxy.py
Normal file
70
tests/tests-httpd/test_10_proxy.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue