Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions test/modules/ssl/test_002_tls13.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import re

import pytest

from pyhttpd.conf import HttpdConf


class TestTLS13:

@pytest.fixture(autouse=True, scope='class')
def _class_scope(self, env):
conf = HttpdConf(env, extras={
f"test1.{env.http_tld}": "SSLProtocol TLSv1.3",
})
conf.add_vhost_test1()
conf.install()
assert env.apache_restart() == 0

def _configure_tls(self, env, protocol="TLSv1.3", ciphersuite=None):
extras = f"SSLProtocol {protocol}\n"
if ciphersuite:
extras += f"SSLCipherSuite TLSv1.3 {ciphersuite}\n"
conf = HttpdConf(env, extras={
f"test1.{env.http_tld}": extras,
})
conf.add_vhost_test1()
conf.install()
assert env.apache_restart() == 0

# TLS 1.3 basic connectivity: server configured for TLSv1.3, client
# connects with -tls1_3 and the negotiated protocol must be TLSv1.3.
def test_ssl_002_01(self, env):
self._configure_tls(env, protocol="TLSv1.3")
r = env.run(args=[
'openssl', 's_client',
'-connect', f'localhost:{env.https_port}',
'-tls1_3',
], intext='')
assert r.exit_code == 0, f"TLS 1.3 handshake failed: {r.stderr}"
output = r.stdout + r.stderr
assert re.search(r'Protocol\s*:\s*TLSv1\.3', output), \
f"TLSv1.3 not negotiated, output: {output}"

# Single server-side cipher: server offers only TLS_AES_256_GCM_SHA384,
# client connects without cipher restriction and must negotiate that cipher.
def test_ssl_002_02(self, env):
self._configure_tls(env, protocol="TLSv1.3",
ciphersuite="TLS_AES_256_GCM_SHA384")
r = env.run(args=[
'openssl', 's_client',
'-connect', f'localhost:{env.https_port}',
'-tls1_3',
], intext='')
assert r.exit_code == 0, f"handshake failed: {r.stderr}"
output = r.stdout + r.stderr
assert 'TLS_AES_256_GCM_SHA384' in output, \
f"expected TLS_AES_256_GCM_SHA384, got: {output}"

# Single client-side cipher: server uses default ciphers, client restricts
# to TLS_AES_256_GCM_SHA384 and must negotiate successfully with that cipher.
def test_ssl_002_03(self, env):
self._configure_tls(env, protocol="TLSv1.3")
r = env.run(args=[
'openssl', 's_client',
'-connect', f'localhost:{env.https_port}',
'-tls1_3',
'-ciphersuites', 'TLS_AES_256_GCM_SHA384',
], intext='')
assert r.exit_code == 0, f"handshake failed: {r.stderr}"
output = r.stdout + r.stderr
assert 'TLS_AES_256_GCM_SHA384' in output, \
f"expected TLS_AES_256_GCM_SHA384, got: {output}"

# Cipher mismatch: server allows only TLS_CHACHA20_POLY1305_SHA256, client
# sends only TLS_AES_256_GCM_SHA384. The handshake must fail.
def test_ssl_002_04(self, env):
self._configure_tls(env, protocol="TLSv1.3",
ciphersuite="TLS_CHACHA20_POLY1305_SHA256")
env.httpd_error_log.add_ignored_lognos(["AH10373"])
env.httpd_error_log.add_ignored_matches([
r'.*SSL Library Error.*',
])
r = env.run(args=[
'openssl', 's_client',
'-connect', f'localhost:{env.https_port}',
'-tls1_3',
'-ciphersuites', 'TLS_AES_256_GCM_SHA384',
], intext='')
output = r.stdout + r.stderr
assert r.exit_code != 0 or 'handshake failure' in output.lower(), \
f"expected handshake failure, got exit_code={r.exit_code}: {output}"

# Version mismatch: server configured for TLSv1.3 only, client forces
# TLSv1.2. The handshake must fail.
def test_ssl_002_05(self, env):
self._configure_tls(env, protocol="-all +TLSv1.3")
env.httpd_error_log.add_ignored_lognos(["AH10373"])
env.httpd_error_log.add_ignored_matches([
r'.*SSL Library Error.*',
])
r = env.run(args=[
'openssl', 's_client',
'-connect', f'localhost:{env.https_port}',
'-tls1_2',
], intext='')
output = r.stdout + r.stderr
assert r.exit_code != 0 or 'handshake failure' in output.lower() \
or 'protocol version' in output.lower(), \
f"expected handshake failure, got exit_code={r.exit_code}: {output}"
52 changes: 52 additions & 0 deletions test/modules/ssl/test_004_engine_optional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import re
import pytest

from pyhttpd.conf import HttpdConf


class TestSslEngineOptional:
"""SSLEngine optional enables TLS upgrade which allows HTTP session
hijacking via a man-in-the-middle attack. The fix removes support
for TLS upgrade, so 'SSLEngine optional' should be rejected."""

def test_ssl_004_01(self, env):
"""httpd should refuse to start with 'SSLEngine optional' and
log that it is no longer supported."""
conf = HttpdConf(env, extras={
f"test1.{env.http_tld}": """
SSLEngine optional
""",
})
conf.add_vhost_test1()
conf.install()
start_pos = env.httpd_error_log.current_pos()
rv = env.apache_restart()
if rv == 0:
found = env.httpd_error_log.wait_for(
re.compile(r".*'SSLEngine optional' is no longer supported.*"),
start_pos, timeout=5)
env.httpd_error_log.ignore_recent(
matches=[r'.*SSLEngine optional.*', r'.*:error\].*',
r'.*:warn\].*'])
if found:
pytest.skip(
"Server started but logged deprecation warning "
"(older httpd that warns but allows)")
else:
pytest.skip(
"Server accepted SSLEngine optional without complaint "
"(pre-fix httpd version)")
else:
found = env.httpd_error_log.wait_for(
re.compile(r".*'SSLEngine optional' is no longer supported.*"),
start_pos, timeout=5)
env.httpd_error_log.ignore_recent(
matches=[r'.*SSLEngine optional.*', r'.*:error\].*',
r'.*:warn\].*', r'.*:emerg\].*'])
assert found, \
"httpd failed to start but did not log the expected " \
"'SSLEngine optional' rejection message"
conf_restore = HttpdConf(env)
conf_restore.add_vhost_test1()
conf_restore.install()
env.apache_restart()