Skip to content

Commit ec8c093

Browse files
committed
Fix SSL verification with ssl_cert_reqs=none and ssl_check_hostname=True
1 parent 37491a4 commit ec8c093

File tree

5 files changed

+87
-2
lines changed

5 files changed

+87
-2
lines changed

CHANGES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
* Close Unix sockets if the connection attempt fails. This prevents `ResourceWarning`s. (#3314)
7171
* Close SSL sockets if the connection attempt fails, or if validations fail. (#3317)
7272
* Eliminate mutable default arguments in the `redis.commands.core.Script` class. (#3332)
73+
* Fix SSL verification with `ssl_cert_reqs="none"` and `ssl_check_hostname=True` by automatically setting `check_hostname=False` when `verify_mode=ssl.CERT_NONE` (#3636)
7374

7475
* 4.1.3 (Feb 8, 2022)
7576
* Fix flushdb and flushall (#1926)

redis/asyncio/connection.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,10 @@ def __init__(
901901
def get(self) -> SSLContext:
902902
if not self.context:
903903
context = ssl.create_default_context()
904-
context.check_hostname = self.check_hostname
904+
if self.cert_reqs == ssl.CERT_NONE:
905+
context.check_hostname = False
906+
else:
907+
context.check_hostname = self.check_hostname
905908
context.verify_mode = self.cert_reqs
906909
if self.certfile and self.keyfile:
907910
context.load_cert_chain(certfile=self.certfile, keyfile=self.keyfile)

redis/connection.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,10 @@ def _wrap_socket_with_ssl(self, sock):
11151115
An SSL wrapped socket.
11161116
"""
11171117
context = ssl.create_default_context()
1118-
context.check_hostname = self.check_hostname
1118+
if self.cert_reqs == ssl.CERT_NONE:
1119+
context.check_hostname = False
1120+
else:
1121+
context.check_hostname = self.check_hostname
11191122
context.verify_mode = self.cert_reqs
11201123
if self.certfile or self.keyfile:
11211124
context.load_cert_chain(

tests/test_asyncio/test_ssl.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import asyncio
2+
import ssl
3+
import socket
4+
from urllib.parse import urlparse
5+
import pytest
6+
import pytest_asyncio
7+
import redis.asyncio as redis
8+
from redis.exceptions import RedisError, ConnectionError
9+
import ssl
10+
11+
# Skip test or not based on cryptography installation
12+
try:
13+
import cryptography # noqa
14+
skip_if_cryptography = pytest.mark.skipif(False, reason="")
15+
skip_if_nocryptography = pytest.mark.skipif(False, reason="")
16+
except ImportError:
17+
skip_if_cryptography = pytest.mark.skipif(True, reason="cryptography not installed")
18+
skip_if_nocryptography = pytest.mark.skipif(True, reason="cryptography not installed")
19+
20+
@pytest.mark.ssl
21+
class TestSSL:
22+
"""Tests for SSL connections in asyncio."""
23+
24+
@pytest_asyncio.fixture()
25+
async def _get_client(self, request):
26+
ssl_url = request.config.option.redis_ssl_url
27+
p = urlparse(ssl_url)[1].split(":")
28+
client = redis.Redis(host=p[0], port=p[1], ssl=True)
29+
yield client
30+
await client.aclose()
31+
32+
async def test_ssl_with_invalid_cert(self, _get_client):
33+
"""Test SSL connection with invalid certificate."""
34+
pass
35+
36+
async def test_cert_reqs_none_with_check_hostname(self, request):
37+
"""Test that when ssl_cert_reqs=none is used with ssl_check_hostname=True,
38+
the connection is created successfully with check_hostname internally set to False"""
39+
ssl_url = request.config.option.redis_ssl_url
40+
p = urlparse(ssl_url)[1].split(":")
41+
r = redis.Redis(
42+
host=p[0],
43+
port=p[1],
44+
ssl=True,
45+
ssl_cert_reqs="none",
46+
ssl_check_hostname=True, # This should work now because we handle the incompatibility
47+
)
48+
try:
49+
# Connection should be successful
50+
assert await r.ping()
51+
# check_hostname should have been automatically set to False
52+
assert r.connection_pool.connection_class == redis.SSLConnection
53+
conn = r.connection_pool.make_connection()
54+
assert conn.check_hostname is False
55+
finally:
56+
await r.aclose()

tests/test_ssl.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,25 @@ def test_mock_ocsp_staple(self, request):
309309
r.ping()
310310
assert "no ocsp response present" in str(e)
311311
r.close()
312+
313+
def test_cert_reqs_none_with_check_hostname(self, request):
314+
"""Test that when ssl_cert_reqs=none is used with ssl_check_hostname=True,
315+
the connection is created successfully with check_hostname internally set to False"""
316+
ssl_url = request.config.option.redis_ssl_url
317+
p = urlparse(ssl_url)[1].split(":")
318+
r = redis.Redis(
319+
host=p[0],
320+
port=p[1],
321+
ssl=True,
322+
ssl_cert_reqs="none",
323+
ssl_check_hostname=True, # This should work now because we handle the incompatibility
324+
)
325+
try:
326+
# Connection should be successful
327+
assert r.ping()
328+
# check_hostname should have been automatically set to False
329+
assert r.connection_pool.connection_kwargs["connection_class"] == redis.SSLConnection
330+
conn = r.connection_pool.make_connection()
331+
assert conn.check_hostname is False
332+
finally:
333+
r.close()

0 commit comments

Comments
 (0)