From 74a8c4103d7e04cccbcf02384eb0a4da6f99eb5e Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Wed, 10 Dec 2025 16:44:15 +0800 Subject: [PATCH 1/2] Add ssl_password support to async Redis client (#3347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ssl_password parameter to the async Redis client and SSLConnection to enable using encrypted private keys, matching the sync client's functionality. Changes: - Add ssl_password parameter to Redis.__init__ - Add ssl_password parameter to SSLConnection.__init__ - Add password parameter to RedisSSLContext - Pass password to ssl.SSLContext.load_cert_chain() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- redis/asyncio/client.py | 2 ++ redis/asyncio/connection.py | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/redis/asyncio/client.py b/redis/asyncio/client.py index de2d7a0dd9..795353fece 100644 --- a/redis/asyncio/client.py +++ b/redis/asyncio/client.py @@ -246,6 +246,7 @@ def __init__( ssl_check_hostname: bool = True, ssl_min_version: Optional[TLSVersion] = None, ssl_ciphers: Optional[str] = None, + ssl_password: Optional[str] = None, max_connections: Optional[int] = None, single_connection_client: bool = False, health_check_interval: int = 0, @@ -357,6 +358,7 @@ def __init__( "ssl_check_hostname": ssl_check_hostname, "ssl_min_version": ssl_min_version, "ssl_ciphers": ssl_ciphers, + "ssl_password": ssl_password, } ) # This arg only used if no pool is passed in diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index 1d50b53ee2..251a68c56c 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -816,6 +816,7 @@ def __init__( ssl_check_hostname: bool = True, ssl_min_version: Optional[TLSVersion] = None, ssl_ciphers: Optional[str] = None, + ssl_password: Optional[str] = None, **kwargs, ): if not SSL_AVAILABLE: @@ -832,6 +833,7 @@ def __init__( check_hostname=ssl_check_hostname, min_version=ssl_min_version, ciphers=ssl_ciphers, + password=ssl_password, ) super().__init__(**kwargs) @@ -890,6 +892,7 @@ class RedisSSLContext: "check_hostname", "min_version", "ciphers", + "password", ) def __init__( @@ -904,6 +907,7 @@ def __init__( check_hostname: bool = False, min_version: Optional[TLSVersion] = None, ciphers: Optional[str] = None, + password: Optional[str] = None, ): if not SSL_AVAILABLE: raise RedisError("Python wasn't built with SSL support") @@ -933,6 +937,7 @@ def __init__( ) self.min_version = min_version self.ciphers = ciphers + self.password = password self.context: Optional[SSLContext] = None def get(self) -> SSLContext: @@ -946,8 +951,12 @@ def get(self) -> SSLContext: if self.exclude_verify_flags: for flag in self.exclude_verify_flags: context.verify_flags &= ~flag - if self.certfile and self.keyfile: - context.load_cert_chain(certfile=self.certfile, keyfile=self.keyfile) + if self.certfile or self.keyfile: + context.load_cert_chain( + certfile=self.certfile, + keyfile=self.keyfile, + password=self.password, + ) if self.ca_certs or self.ca_data: context.load_verify_locations(cafile=self.ca_certs, cadata=self.ca_data) if self.min_version is not None: From 6a22d217a578ca803e4c25bc10ccb2ffe893f2a5 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Thu, 11 Dec 2025 10:08:15 +0800 Subject: [PATCH 2/2] Add test for ssl_password parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test_ssl_password_parameter test to verify ssl_password is properly passed through to SSLConnection for encrypted private key support. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- tests/test_asyncio/test_ssl.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_asyncio/test_ssl.py b/tests/test_asyncio/test_ssl.py index 154d20a9ea..e98b8911f2 100644 --- a/tests/test_asyncio/test_ssl.py +++ b/tests/test_asyncio/test_ssl.py @@ -141,3 +141,29 @@ def capture_context_create_default(): finally: await r.aclose() + + async def test_ssl_password_parameter(self, request): + """Test that ssl_password parameter is properly passed to SSLConnection""" + ssl_url = request.config.option.redis_ssl_url + parsed_url = urlparse(ssl_url) + + # Test with a mock password for encrypted private key + test_password = "test_key_password" + + r = redis.Redis( + host=parsed_url.hostname, + port=parsed_url.port, + ssl=True, + ssl_cert_reqs="none", + ssl_password=test_password, + ) + + try: + # Get the connection to verify ssl_password is passed through + conn = r.connection_pool.make_connection() + assert isinstance(conn, redis.SSLConnection) + + # Verify the password is stored in the SSL context + assert conn.ssl_context.password == test_password + finally: + await r.aclose()