From 589bf6d959c99400af74dd4e27aab908dc4c14c5 Mon Sep 17 00:00:00 2001 From: dvora-h Date: Mon, 14 Mar 2022 01:24:39 +0200 Subject: [PATCH 1/4] ssl string cert --- redis/asyncio/client.py | 2 ++ redis/asyncio/connection.py | 8 ++++++-- redis/client.py | 2 ++ redis/cluster.py | 1 + redis/connection.py | 13 +++++++++++-- tests/test_ssl.py | 18 +++++++++++++++++- 6 files changed, 39 insertions(+), 5 deletions(-) diff --git a/redis/asyncio/client.py b/redis/asyncio/client.py index 2afad0f4d1..bbf2a676ed 100644 --- a/redis/asyncio/client.py +++ b/redis/asyncio/client.py @@ -163,6 +163,7 @@ def __init__( ssl_certfile: Optional[str] = None, ssl_cert_reqs: str = "required", ssl_ca_certs: Optional[str] = None, + ssl_ca_data: Optional[str] = None, ssl_check_hostname: bool = False, max_connections: Optional[int] = None, single_connection_client: bool = False, @@ -228,6 +229,7 @@ def __init__( "ssl_certfile": ssl_certfile, "ssl_cert_reqs": ssl_cert_reqs, "ssl_ca_certs": ssl_ca_certs, + "ssl_ca_data": ssl_ca_data, "ssl_check_hostname": ssl_check_hostname, } ) diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index ae54d8147a..0c9c4424b6 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -1026,6 +1026,7 @@ def __init__( ssl_certfile: Optional[str] = None, ssl_cert_reqs: str = "required", ssl_ca_certs: Optional[str] = None, + ssl_ca_data: Optional[str] = None, ssl_check_hostname: bool = False, **kwargs, ): @@ -1035,6 +1036,7 @@ def __init__( certfile=ssl_certfile, cert_reqs=ssl_cert_reqs, ca_certs=ssl_ca_certs, + ca_data=ssl_ca_data, check_hostname=ssl_check_hostname, ) @@ -1075,6 +1077,7 @@ def __init__( certfile: Optional[str] = None, cert_reqs: Optional[str] = None, ca_certs: Optional[str] = None, + ca_data: Optional[str] = None, check_hostname: bool = False, ): self.keyfile = keyfile @@ -1093,6 +1096,7 @@ def __init__( ) self.cert_reqs = CERT_REQS[cert_reqs] self.ca_certs = ca_certs + self.ca_data = ca_data self.check_hostname = check_hostname self.context: Optional[ssl.SSLContext] = None @@ -1103,8 +1107,8 @@ def get(self) -> ssl.SSLContext: context.verify_mode = self.cert_reqs if self.certfile and self.keyfile: context.load_cert_chain(certfile=self.certfile, keyfile=self.keyfile) - if self.ca_certs: - context.load_verify_locations(self.ca_certs) + if self.ca_certs or self.ca_data: + context.load_verify_locations(cafile=self.ca_certs, cadata=self.ca_data) self.context = context return self.context diff --git a/redis/client.py b/redis/client.py index b12ad578d6..4b81ac3638 100755 --- a/redis/client.py +++ b/redis/client.py @@ -883,6 +883,7 @@ def __init__( ssl_cert_reqs="required", ssl_ca_certs=None, ssl_ca_path=None, + ssl_ca_data=None, ssl_check_hostname=False, ssl_password=None, ssl_validate_ocsp=False, @@ -964,6 +965,7 @@ def __init__( "ssl_certfile": ssl_certfile, "ssl_cert_reqs": ssl_cert_reqs, "ssl_ca_certs": ssl_ca_certs, + "ssl_ca_data": ssl_ca_data, "ssl_check_hostname": ssl_check_hostname, "ssl_password": ssl_password, "ssl_ca_path": ssl_ca_path, diff --git a/redis/cluster.py b/redis/cluster.py index 8c2dfc271b..b116c3b496 100644 --- a/redis/cluster.py +++ b/redis/cluster.py @@ -116,6 +116,7 @@ def fix_server(*args): "socket_timeout", "ssl", "ssl_ca_certs", + "ssl_ca_data", "ssl_certfile", "ssl_cert_reqs", "ssl_keyfile", diff --git a/redis/connection.py b/redis/connection.py index 8a77e4bdee..0f97361e6e 100755 --- a/redis/connection.py +++ b/redis/connection.py @@ -923,6 +923,7 @@ def __init__( ssl_certfile=None, ssl_cert_reqs="required", ssl_ca_certs=None, + ssl_ca_data=None, ssl_check_hostname=False, ssl_ca_path=None, ssl_password=None, @@ -939,6 +940,7 @@ def __init__( ssl_certfile: Path to an ssl certificate. Defaults to None. ssl_cert_reqs: The string value for the SSLContext.verify_mode (none, optional, required). Defaults to "required". ssl_ca_certs: The path to a file of concatenated CA certificates in PEM format. Defaults to None. + ssl_ca_data: Either an ASCII string of one or more PEM-encoded certificates or a bytes-like object of DER-encoded certificates. ssl_check_hostname: If set, match the hostname during the SSL handshake. Defaults to False. ssl_ca_path: The path to a directory containing several CA certificates in PEM format. Defaults to None. ssl_password: Password for unlocking an encrypted private key. Defaults to None. @@ -973,6 +975,7 @@ def __init__( ssl_cert_reqs = CERT_REQS[ssl_cert_reqs] self.cert_reqs = ssl_cert_reqs self.ca_certs = ssl_ca_certs + self.ca_data = ssl_ca_data self.ca_path = ssl_ca_path self.check_hostname = ssl_check_hostname self.certificate_password = ssl_password @@ -993,8 +996,14 @@ def _connect(self): keyfile=self.keyfile, password=self.certificate_password, ) - if self.ca_certs is not None or self.ca_path is not None: - context.load_verify_locations(cafile=self.ca_certs, capath=self.ca_path) + if ( + self.ca_certs is not None + or self.ca_path is not None + or self.ca_data is not None + ): + context.load_verify_locations( + cafile=self.ca_certs, capath=self.ca_path, cadata=self.ca_data + ) sslsock = context.wrap_socket(sock, server_hostname=self.host) if self.ssl_validate_ocsp is True and CRYPTOGRAPHY_AVAILABLE is False: raise RedisError("cryptography is not installed.") diff --git a/tests/test_ssl.py b/tests/test_ssl.py index ab5d47f293..304f25add5 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -53,7 +53,7 @@ def test_ssl_connection_without_ssl(self, request): r.ping() assert "Connection closed by server" in str(e) - def test_validating_self_signed_certificate(self, request): + def test_validating_self_signed_file_certificate(self, request): ssl_url = request.config.option.redis_ssl_url p = urlparse(ssl_url)[1].split(":") r = redis.Redis( @@ -67,6 +67,22 @@ def test_validating_self_signed_certificate(self, request): ) assert r.ping() + def test_validating_self_signed_string_certificate(self, request): + f = open(self.SERVER_CERT) + cert_data = f.read() + ssl_url = request.config.option.redis_ssl_url + p = urlparse(ssl_url)[1].split(":") + r = redis.Redis( + host=p[0], + port=p[1], + ssl=True, + ssl_certfile=self.SERVER_CERT, + ssl_keyfile=self.SERVER_KEY, + ssl_cert_reqs="required", + ssl_ca_data=cert_data, + ) + assert r.ping() + def _create_oscp_conn(self, request): ssl_url = request.config.option.redis_ssl_url p = urlparse(ssl_url)[1].split(":") From 62f15df09c813c7092851c860199b7d813d667e4 Mon Sep 17 00:00:00 2001 From: dvora-h Date: Mon, 14 Mar 2022 01:43:12 +0200 Subject: [PATCH 2/4] fix async test --- redis/asyncio/connection.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index 0c9c4424b6..e3b760aadf 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -1055,6 +1055,10 @@ def cert_reqs(self): @property def ca_certs(self): return self.ssl_context.ca_certs + + @property + def ca_data(self): + return self.ssl_context.ca_data @property def check_hostname(self): @@ -1067,6 +1071,7 @@ class RedisSSLContext: "certfile", "cert_reqs", "ca_certs", + "ca_data", "context", "check_hostname", ) From 1e3d2ee8294ff53bbe23de4052d613b7819d8fb3 Mon Sep 17 00:00:00 2001 From: dvora-h Date: Mon, 14 Mar 2022 01:46:50 +0200 Subject: [PATCH 3/4] linters --- redis/asyncio/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index e3b760aadf..830dfcd889 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -1055,7 +1055,7 @@ def cert_reqs(self): @property def ca_certs(self): return self.ssl_context.ca_certs - + @property def ca_data(self): return self.ssl_context.ca_data From b8383b32204c47d31341d5cc5ae01d29eb540fb8 Mon Sep 17 00:00:00 2001 From: dvora-h <67596500+dvora-h@users.noreply.github.com> Date: Mon, 14 Mar 2022 13:48:46 +0200 Subject: [PATCH 4/4] change test name --- tests/test_ssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_ssl.py b/tests/test_ssl.py index 304f25add5..d029b80dcb 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -53,7 +53,7 @@ def test_ssl_connection_without_ssl(self, request): r.ping() assert "Connection closed by server" in str(e) - def test_validating_self_signed_file_certificate(self, request): + def test_validating_self_signed_certificate(self, request): ssl_url = request.config.option.redis_ssl_url p = urlparse(ssl_url)[1].split(":") r = redis.Redis(