Skip to content

Commit

Permalink
https-proxy: use IP address and cert with ip in alt names
Browse files Browse the repository at this point in the history
- improve info logging when peer verification fails to indicate
  if DNS name or ip address has been tried to match
- add test case for contacting https proxy with ip address
- add pytest env check on loaded credentials and re-issue
  when they are no longer valid
- disable proxy ip address test for bearssl, since not supported there

Ref: #12831
Closes #12838
  • Loading branch information
icing authored and bagder committed Feb 6, 2024
1 parent 4f79455 commit c177e19
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 14 deletions.
6 changes: 4 additions & 2 deletions lib/vtls/openssl.c
Expand Up @@ -2242,9 +2242,11 @@ CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
/* an alternative name matched */
;
else if(dNSName || iPAddress) {
infof(data, " subjectAltName does not match %s", peer->dispname);
infof(data, " subjectAltName does not match %s %s",
peer->is_ip_address? "ip address" : "host name", peer->dispname);
failf(data, "SSL: no alternative certificate subject name matches "
"target host name '%s'", peer->dispname);
"target %s '%s'",
peer->is_ip_address? "ip address" : "host name", peer->dispname);
result = CURLE_PEER_FAILED_VERIFICATION;
}
else {
Expand Down
20 changes: 18 additions & 2 deletions tests/http/test_10_proxy.py
Expand Up @@ -70,8 +70,7 @@ def test_10_01_proxy_http(self, env: Env, httpd, repeat):
@pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
reason='curl lacks HTTPS-proxy support')
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
@pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
def test_10_02_proxys_down(self, env: Env, httpd, nghttpx_fwd, proto, repeat):
def test_10_02_proxys_down(self, env: Env, httpd, proto, repeat):
if proto == 'h2' and not env.curl_uses_lib('nghttp2'):
pytest.skip('only supported with nghttp2')
curl = CurlClient(env=env)
Expand Down Expand Up @@ -349,3 +348,20 @@ def test_10_13_noreuse_https(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat)
extra_args=x2_args)
r2.check_response(count=2, http_status=200)
assert r2.total_connects == 2

# download via https: proxy (no tunnel) using IP address
@pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
reason='curl lacks HTTPS-proxy support')
@pytest.mark.skipif(condition=Env.curl_uses_lib('bearssl'), reason="ip address cert verification not supported")
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
def test_10_14_proxys_ip_addr(self, env: Env, httpd, proto, repeat):
if proto == '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'
xargs = curl.get_proxy_args(proto=proto, use_ip=True)
r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
extra_args=xargs)
r.check_response(count=1, http_status=200,
protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')

23 changes: 19 additions & 4 deletions tests/http/testenv/certs.py
Expand Up @@ -24,6 +24,7 @@
#
###########################################################################
#
import ipaddress
import os
import re
from datetime import timedelta, datetime
Expand Down Expand Up @@ -79,6 +80,7 @@ def __init__(self, name: Optional[str] = None,
valid_from: timedelta = timedelta(days=-1),
valid_to: timedelta = timedelta(days=89),
client: bool = False,
check_valid: bool = True,
sub_specs: Optional[List['CertificateSpec']] = None):
self._name = name
self.domains = domains
Expand All @@ -89,6 +91,7 @@ def __init__(self, name: Optional[str] = None,
self.valid_from = valid_from
self.valid_to = valid_to
self.sub_specs = sub_specs
self.check_valid = check_valid

@property
def name(self) -> Optional[str]:
Expand Down Expand Up @@ -202,7 +205,8 @@ def issue_cert(self, spec: CertificateSpec,
creds = None
if self._store:
creds = self._store.load_credentials(
name=spec.name, key_type=key_type, single_file=spec.single_file, issuer=self)
name=spec.name, key_type=key_type, single_file=spec.single_file,
issuer=self, check_valid=spec.check_valid)
if creds is None:
creds = TestCA.create_credentials(spec=spec, issuer=self, key_type=key_type,
valid_from=spec.valid_from, valid_to=spec.valid_to)
Expand Down Expand Up @@ -303,13 +307,18 @@ def load_pem_pkey(self, fpath: str):

def load_credentials(self, name: str, key_type=None,
single_file: bool = False,
issuer: Optional[Credentials] = None):
issuer: Optional[Credentials] = None,
check_valid: bool = False):
cert_file = self.get_cert_file(name=name, key_type=key_type)
pkey_file = cert_file if single_file else self.get_pkey_file(name=name, key_type=key_type)
comb_file = self.get_combined_file(name=name, key_type=key_type)
if os.path.isfile(cert_file) and os.path.isfile(pkey_file):
cert = self.load_pem_cert(cert_file)
pkey = self.load_pem_pkey(pkey_file)
if check_valid and \
((cert.not_valid_after < datetime.now()) or
(cert.not_valid_before > datetime.now())):
return None
creds = Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer)
creds.set_store(self)
creds.set_files(cert_file, pkey_file, comb_file)
Expand Down Expand Up @@ -426,6 +435,13 @@ def _add_ca_usages(csr: Any) -> Any:

@staticmethod
def _add_leaf_usages(csr: Any, domains: List[str], issuer: Credentials) -> Any:
names = []
for name in domains:
try:
names.append(x509.IPAddress(ipaddress.ip_address(name)))
except:
names.append(x509.DNSName(name))

return csr.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
Expand All @@ -435,8 +451,7 @@ def _add_leaf_usages(csr: Any, domains: List[str], issuer: Credentials) -> Any:
x509.SubjectKeyIdentifier).value),
critical=False
).add_extension(
x509.SubjectAlternativeName([x509.DNSName(domain) for domain in domains]),
critical=True,
x509.SubjectAlternativeName(names), critical=True,
).add_extension(
x509.ExtendedKeyUsage([
ExtendedKeyUsageOID.SERVER_AUTH,
Expand Down
12 changes: 7 additions & 5 deletions tests/http/testenv/curl.py
Expand Up @@ -393,20 +393,22 @@ def _mkpath(self, path):
return os.makedirs(path)

def get_proxy_args(self, proto: str = 'http/1.1',
proxys: bool = True, tunnel: bool = False):
proxys: bool = True, tunnel: bool = False,
use_ip: bool = False):
proxy_name = '127.0.0.1' if use_ip else self.env.proxy_domain
if proxys:
pport = self.env.pts_port(proto) if tunnel else self.env.proxys_port
xargs = [
'--proxy', f'https://{self.env.proxy_domain}:{pport}/',
'--resolve', f'{self.env.proxy_domain}:{pport}:127.0.0.1',
'--proxy', f'https://{proxy_name}:{pport}/',
'--resolve', f'{proxy_name}:{pport}:127.0.0.1',
'--proxy-cacert', self.env.ca.cert_file,
]
if proto == 'h2':
xargs.append('--proxy-http2')
else:
xargs = [
'--proxy', f'http://{self.env.proxy_domain}:{self.env.proxy_port}/',
'--resolve', f'{self.env.proxy_domain}:{self.env.proxy_port}:127.0.0.1',
'--proxy', f'http://{proxy_name}:{self.env.proxy_port}/',
'--resolve', f'{proxy_name}:{self.env.proxy_port}:127.0.0.1',
]
if tunnel:
xargs.append('--proxytunnel')
Expand Down
3 changes: 2 additions & 1 deletion tests/http/testenv/env.py
Expand Up @@ -31,6 +31,7 @@
import subprocess
import sys
from configparser import ConfigParser, ExtendedInterpolation
from datetime import timedelta
from typing import Optional

import pytest
Expand Down Expand Up @@ -133,7 +134,7 @@ def __init__(self):
self.cert_specs = [
CertificateSpec(domains=[self.domain1, 'localhost'], key_type='rsa2048'),
CertificateSpec(domains=[self.domain2], key_type='rsa2048'),
CertificateSpec(domains=[self.proxy_domain], key_type='rsa2048'),
CertificateSpec(domains=[self.proxy_domain, '127.0.0.1'], key_type='rsa2048'),
CertificateSpec(name="clientsX", sub_specs=[
CertificateSpec(name="user1", client=True),
]),
Expand Down

0 comments on commit c177e19

Please sign in to comment.