Skip to content

Commit a12b5fd

Browse files
authored
Add support to use certificates from string in ssl connection (#2048)
* ssl string cert * fix async test * linters * change test name
1 parent a081173 commit a12b5fd

File tree

6 files changed

+43
-4
lines changed

6 files changed

+43
-4
lines changed

redis/asyncio/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def __init__(
163163
ssl_certfile: Optional[str] = None,
164164
ssl_cert_reqs: str = "required",
165165
ssl_ca_certs: Optional[str] = None,
166+
ssl_ca_data: Optional[str] = None,
166167
ssl_check_hostname: bool = False,
167168
max_connections: Optional[int] = None,
168169
single_connection_client: bool = False,
@@ -228,6 +229,7 @@ def __init__(
228229
"ssl_certfile": ssl_certfile,
229230
"ssl_cert_reqs": ssl_cert_reqs,
230231
"ssl_ca_certs": ssl_ca_certs,
232+
"ssl_ca_data": ssl_ca_data,
231233
"ssl_check_hostname": ssl_check_hostname,
232234
}
233235
)

redis/asyncio/connection.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,7 @@ def __init__(
10261026
ssl_certfile: Optional[str] = None,
10271027
ssl_cert_reqs: str = "required",
10281028
ssl_ca_certs: Optional[str] = None,
1029+
ssl_ca_data: Optional[str] = None,
10291030
ssl_check_hostname: bool = False,
10301031
**kwargs,
10311032
):
@@ -1035,6 +1036,7 @@ def __init__(
10351036
certfile=ssl_certfile,
10361037
cert_reqs=ssl_cert_reqs,
10371038
ca_certs=ssl_ca_certs,
1039+
ca_data=ssl_ca_data,
10381040
check_hostname=ssl_check_hostname,
10391041
)
10401042

@@ -1054,6 +1056,10 @@ def cert_reqs(self):
10541056
def ca_certs(self):
10551057
return self.ssl_context.ca_certs
10561058

1059+
@property
1060+
def ca_data(self):
1061+
return self.ssl_context.ca_data
1062+
10571063
@property
10581064
def check_hostname(self):
10591065
return self.ssl_context.check_hostname
@@ -1065,6 +1071,7 @@ class RedisSSLContext:
10651071
"certfile",
10661072
"cert_reqs",
10671073
"ca_certs",
1074+
"ca_data",
10681075
"context",
10691076
"check_hostname",
10701077
)
@@ -1075,6 +1082,7 @@ def __init__(
10751082
certfile: Optional[str] = None,
10761083
cert_reqs: Optional[str] = None,
10771084
ca_certs: Optional[str] = None,
1085+
ca_data: Optional[str] = None,
10781086
check_hostname: bool = False,
10791087
):
10801088
self.keyfile = keyfile
@@ -1093,6 +1101,7 @@ def __init__(
10931101
)
10941102
self.cert_reqs = CERT_REQS[cert_reqs]
10951103
self.ca_certs = ca_certs
1104+
self.ca_data = ca_data
10961105
self.check_hostname = check_hostname
10971106
self.context: Optional[ssl.SSLContext] = None
10981107

@@ -1103,8 +1112,8 @@ def get(self) -> ssl.SSLContext:
11031112
context.verify_mode = self.cert_reqs
11041113
if self.certfile and self.keyfile:
11051114
context.load_cert_chain(certfile=self.certfile, keyfile=self.keyfile)
1106-
if self.ca_certs:
1107-
context.load_verify_locations(self.ca_certs)
1115+
if self.ca_certs or self.ca_data:
1116+
context.load_verify_locations(cafile=self.ca_certs, cadata=self.ca_data)
11081117
self.context = context
11091118
return self.context
11101119

redis/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,7 @@ def __init__(
885885
ssl_cert_reqs="required",
886886
ssl_ca_certs=None,
887887
ssl_ca_path=None,
888+
ssl_ca_data=None,
888889
ssl_check_hostname=False,
889890
ssl_password=None,
890891
ssl_validate_ocsp=False,
@@ -966,6 +967,7 @@ def __init__(
966967
"ssl_certfile": ssl_certfile,
967968
"ssl_cert_reqs": ssl_cert_reqs,
968969
"ssl_ca_certs": ssl_ca_certs,
970+
"ssl_ca_data": ssl_ca_data,
969971
"ssl_check_hostname": ssl_check_hostname,
970972
"ssl_password": ssl_password,
971973
"ssl_ca_path": ssl_ca_path,

redis/cluster.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ def fix_server(*args):
116116
"socket_timeout",
117117
"ssl",
118118
"ssl_ca_certs",
119+
"ssl_ca_data",
119120
"ssl_certfile",
120121
"ssl_cert_reqs",
121122
"ssl_keyfile",

redis/connection.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,7 @@ def __init__(
923923
ssl_certfile=None,
924924
ssl_cert_reqs="required",
925925
ssl_ca_certs=None,
926+
ssl_ca_data=None,
926927
ssl_check_hostname=False,
927928
ssl_ca_path=None,
928929
ssl_password=None,
@@ -939,6 +940,7 @@ def __init__(
939940
ssl_certfile: Path to an ssl certificate. Defaults to None.
940941
ssl_cert_reqs: The string value for the SSLContext.verify_mode (none, optional, required). Defaults to "required".
941942
ssl_ca_certs: The path to a file of concatenated CA certificates in PEM format. Defaults to None.
943+
ssl_ca_data: Either an ASCII string of one or more PEM-encoded certificates or a bytes-like object of DER-encoded certificates.
942944
ssl_check_hostname: If set, match the hostname during the SSL handshake. Defaults to False.
943945
ssl_ca_path: The path to a directory containing several CA certificates in PEM format. Defaults to None.
944946
ssl_password: Password for unlocking an encrypted private key. Defaults to None.
@@ -973,6 +975,7 @@ def __init__(
973975
ssl_cert_reqs = CERT_REQS[ssl_cert_reqs]
974976
self.cert_reqs = ssl_cert_reqs
975977
self.ca_certs = ssl_ca_certs
978+
self.ca_data = ssl_ca_data
976979
self.ca_path = ssl_ca_path
977980
self.check_hostname = ssl_check_hostname
978981
self.certificate_password = ssl_password
@@ -993,8 +996,14 @@ def _connect(self):
993996
keyfile=self.keyfile,
994997
password=self.certificate_password,
995998
)
996-
if self.ca_certs is not None or self.ca_path is not None:
997-
context.load_verify_locations(cafile=self.ca_certs, capath=self.ca_path)
999+
if (
1000+
self.ca_certs is not None
1001+
or self.ca_path is not None
1002+
or self.ca_data is not None
1003+
):
1004+
context.load_verify_locations(
1005+
cafile=self.ca_certs, capath=self.ca_path, cadata=self.ca_data
1006+
)
9981007
sslsock = context.wrap_socket(sock, server_hostname=self.host)
9991008
if self.ssl_validate_ocsp is True and CRYPTOGRAPHY_AVAILABLE is False:
10001009
raise RedisError("cryptography is not installed.")

tests/test_ssl.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ def test_validating_self_signed_certificate(self, request):
6767
)
6868
assert r.ping()
6969

70+
def test_validating_self_signed_string_certificate(self, request):
71+
f = open(self.SERVER_CERT)
72+
cert_data = f.read()
73+
ssl_url = request.config.option.redis_ssl_url
74+
p = urlparse(ssl_url)[1].split(":")
75+
r = redis.Redis(
76+
host=p[0],
77+
port=p[1],
78+
ssl=True,
79+
ssl_certfile=self.SERVER_CERT,
80+
ssl_keyfile=self.SERVER_KEY,
81+
ssl_cert_reqs="required",
82+
ssl_ca_data=cert_data,
83+
)
84+
assert r.ping()
85+
7086
def _create_oscp_conn(self, request):
7187
ssl_url = request.config.option.redis_ssl_url
7288
p = urlparse(ssl_url)[1].split(":")

0 commit comments

Comments
 (0)