Skip to content

Commit 383c711

Browse files
fantixelprans
andauthored
Fix SSL compatibility of libpq (#827)
Co-authored-by: Elvis Pranskevichus <[email protected]>
1 parent 36658fa commit 383c711

10 files changed

+713
-194
lines changed

asyncpg/connect_utils.py

+125-34
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import ssl as ssl_module
1919
import stat
2020
import struct
21+
import sys
2122
import time
2223
import typing
2324
import urllib.parse
@@ -220,13 +221,35 @@ def _parse_hostlist(hostlist, port, *, unquote=False):
220221
return hosts, port
221222

222223

224+
def _parse_tls_version(tls_version):
225+
if not hasattr(ssl_module, 'TLSVersion'):
226+
raise ValueError(
227+
"TLSVersion is not supported in this version of Python"
228+
)
229+
if tls_version.startswith('SSL'):
230+
raise ValueError(
231+
f"Unsupported TLS version: {tls_version}"
232+
)
233+
try:
234+
return ssl_module.TLSVersion[tls_version.replace('.', '_')]
235+
except KeyError:
236+
raise ValueError(
237+
f"No such TLS version: {tls_version}"
238+
)
239+
240+
241+
def _dot_postgresql_path(filename) -> pathlib.Path:
242+
return (pathlib.Path.home() / '.postgresql' / filename).resolve()
243+
244+
223245
def _parse_connect_dsn_and_args(*, dsn, host, port, user,
224246
password, passfile, database, ssl,
225247
connect_timeout, server_settings):
226248
# `auth_hosts` is the version of host information for the purposes
227249
# of reading the pgpass file.
228250
auth_hosts = None
229-
sslcert = sslkey = sslrootcert = sslcrl = None
251+
sslcert = sslkey = sslrootcert = sslcrl = sslpassword = None
252+
ssl_min_protocol_version = ssl_max_protocol_version = None
230253

231254
if dsn:
232255
parsed = urllib.parse.urlparse(dsn)
@@ -312,24 +335,29 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
312335
ssl = val
313336

314337
if 'sslcert' in query:
315-
val = query.pop('sslcert')
316-
if sslcert is None:
317-
sslcert = val
338+
sslcert = query.pop('sslcert')
318339

319340
if 'sslkey' in query:
320-
val = query.pop('sslkey')
321-
if sslkey is None:
322-
sslkey = val
341+
sslkey = query.pop('sslkey')
323342

324343
if 'sslrootcert' in query:
325-
val = query.pop('sslrootcert')
326-
if sslrootcert is None:
327-
sslrootcert = val
344+
sslrootcert = query.pop('sslrootcert')
328345

329346
if 'sslcrl' in query:
330-
val = query.pop('sslcrl')
331-
if sslcrl is None:
332-
sslcrl = val
347+
sslcrl = query.pop('sslcrl')
348+
349+
if 'sslpassword' in query:
350+
sslpassword = query.pop('sslpassword')
351+
352+
if 'ssl_min_protocol_version' in query:
353+
ssl_min_protocol_version = query.pop(
354+
'ssl_min_protocol_version'
355+
)
356+
357+
if 'ssl_max_protocol_version' in query:
358+
ssl_max_protocol_version = query.pop(
359+
'ssl_max_protocol_version'
360+
)
333361

334362
if query:
335363
if server_settings is None:
@@ -451,34 +479,97 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
451479
if sslmode < SSLMode.allow:
452480
ssl = False
453481
else:
454-
ssl = ssl_module.create_default_context(
455-
ssl_module.Purpose.SERVER_AUTH)
482+
ssl = ssl_module.SSLContext(ssl_module.PROTOCOL_TLS_CLIENT)
456483
ssl.check_hostname = sslmode >= SSLMode.verify_full
457-
ssl.verify_mode = ssl_module.CERT_REQUIRED
458-
if sslmode <= SSLMode.require:
484+
if sslmode < SSLMode.require:
459485
ssl.verify_mode = ssl_module.CERT_NONE
486+
else:
487+
if sslrootcert is None:
488+
sslrootcert = os.getenv('PGSSLROOTCERT')
489+
if sslrootcert:
490+
ssl.load_verify_locations(cafile=sslrootcert)
491+
ssl.verify_mode = ssl_module.CERT_REQUIRED
492+
else:
493+
sslrootcert = _dot_postgresql_path('root.crt')
494+
try:
495+
ssl.load_verify_locations(cafile=sslrootcert)
496+
except FileNotFoundError:
497+
if sslmode > SSLMode.require:
498+
raise ValueError(
499+
f'root certificate file "{sslrootcert}" does '
500+
f'not exist\nEither provide the file or '
501+
f'change sslmode to disable server '
502+
f'certificate verification.'
503+
)
504+
elif sslmode == SSLMode.require:
505+
ssl.verify_mode = ssl_module.CERT_NONE
506+
else:
507+
assert False, 'unreachable'
508+
else:
509+
ssl.verify_mode = ssl_module.CERT_REQUIRED
460510

461-
if sslcert is None:
462-
sslcert = os.getenv('PGSSLCERT')
511+
if sslcrl is None:
512+
sslcrl = os.getenv('PGSSLCRL')
513+
if sslcrl:
514+
ssl.load_verify_locations(cafile=sslcrl)
515+
ssl.verify_flags |= ssl_module.VERIFY_CRL_CHECK_CHAIN
516+
else:
517+
sslcrl = _dot_postgresql_path('root.crl')
518+
try:
519+
ssl.load_verify_locations(cafile=sslcrl)
520+
except FileNotFoundError:
521+
pass
522+
else:
523+
ssl.verify_flags |= ssl_module.VERIFY_CRL_CHECK_CHAIN
463524

464525
if sslkey is None:
465526
sslkey = os.getenv('PGSSLKEY')
466-
467-
if sslrootcert is None:
468-
sslrootcert = os.getenv('PGSSLROOTCERT')
469-
470-
if sslcrl is None:
471-
sslcrl = os.getenv('PGSSLCRL')
472-
527+
if not sslkey:
528+
sslkey = _dot_postgresql_path('postgresql.key')
529+
if not sslkey.exists():
530+
sslkey = None
531+
if not sslpassword:
532+
sslpassword = ''
533+
if sslcert is None:
534+
sslcert = os.getenv('PGSSLCERT')
473535
if sslcert:
474-
ssl.load_cert_chain(sslcert, keyfile=sslkey)
475-
476-
if sslrootcert:
477-
ssl.load_verify_locations(cafile=sslrootcert)
478-
479-
if sslcrl:
480-
ssl.load_verify_locations(cafile=sslcrl)
481-
ssl.verify_flags |= ssl_module.VERIFY_CRL_CHECK_CHAIN
536+
ssl.load_cert_chain(
537+
sslcert, keyfile=sslkey, password=lambda: sslpassword
538+
)
539+
else:
540+
sslcert = _dot_postgresql_path('postgresql.crt')
541+
try:
542+
ssl.load_cert_chain(
543+
sslcert, keyfile=sslkey, password=lambda: sslpassword
544+
)
545+
except FileNotFoundError:
546+
pass
547+
548+
# OpenSSL 1.1.1 keylog file, copied from create_default_context()
549+
if hasattr(ssl, 'keylog_filename'):
550+
keylogfile = os.environ.get('SSLKEYLOGFILE')
551+
if keylogfile and not sys.flags.ignore_environment:
552+
ssl.keylog_filename = keylogfile
553+
554+
if ssl_min_protocol_version is None:
555+
ssl_min_protocol_version = os.getenv('PGSSLMINPROTOCOLVERSION')
556+
if ssl_min_protocol_version:
557+
ssl.minimum_version = _parse_tls_version(
558+
ssl_min_protocol_version
559+
)
560+
else:
561+
try:
562+
ssl.minimum_version = _parse_tls_version('TLSv1.2')
563+
except ValueError:
564+
# Python 3.6 does not have ssl.TLSVersion
565+
pass
566+
567+
if ssl_max_protocol_version is None:
568+
ssl_max_protocol_version = os.getenv('PGSSLMAXPROTOCOLVERSION')
569+
if ssl_max_protocol_version:
570+
ssl.maximum_version = _parse_tls_version(
571+
ssl_max_protocol_version
572+
)
482573

483574
elif ssl is True:
484575
ssl = ssl_module.create_default_context()

asyncpg/connection.py

+14
Original file line numberDiff line numberDiff line change
@@ -2020,6 +2020,20 @@ async def connect(dsn=None, *,
20202020
The ``sslcert``, ``sslkey``, ``sslrootcert``, and ``sslcrl`` options
20212021
are supported in the *dsn* argument.
20222022
2023+
.. versionchanged:: 0.25.0
2024+
The ``sslpassword``, ``ssl_min_protocol_version``,
2025+
and ``ssl_max_protocol_version`` options are supported in the *dsn*
2026+
argument.
2027+
2028+
.. versionchanged:: 0.25.0
2029+
Default system root CA certificates won't be loaded when specifying a
2030+
particular sslmode, following the same behavior in libpq.
2031+
2032+
.. versionchanged:: 0.25.0
2033+
The ``sslcert``, ``sslkey``, ``sslrootcert``, and ``sslcrl`` options
2034+
in the *dsn* argument now have consistent default values of files under
2035+
``~/.postgresql/`` as libpq.
2036+
20232037
.. _SSLContext: https://docs.python.org/3/library/ssl.html#ssl.SSLContext
20242038
.. _create_default_context:
20252039
https://docs.python.org/3/library/ssl.html#ssl.create_default_context

tests/certs/ca.cert.pem

+33-33
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
11
-----BEGIN CERTIFICATE-----
2-
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
34-
ztcZLWC9GiyC/TFSJ1hDylkvvwcCX6PD7fLu
2+
MIIGFjCCA/6gAwIBAgIIDAM+rFY5KqgwDQYJKoZIhvcNAQELBQAwgaExCzAJBgNV
3+
BAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdUb3JvbnRvMRgwFgYD
4+
VQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5jcGcgdGVzdHMxHTAb
5+
BgNVBAMMFGFzeW5jcGcgdGVzdCByb290IGNhMR0wGwYJKoZIhvcNAQkBFg5oZWxs
6+
b0BtYWdpYy5pbzAeFw0yMTA5MTMxNjA2MDFaFw00MDExMTMxNjA2MDFaMIGhMQsw
7+
CQYDVQQGEwJDQTEQMA4GA1UECAwHT250YXJpbzEQMA4GA1UEBwwHVG9yb250bzEY
8+
MBYGA1UECgwPTWFnaWNTdGFjayBJbmMuMRYwFAYDVQQLDA1hc3luY3BnIHRlc3Rz
9+
MR0wGwYDVQQDDBRhc3luY3BnIHRlc3Qgcm9vdCBjYTEdMBsGCSqGSIb3DQEJARYO
10+
aGVsbG9AbWFnaWMuaW8wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDK
11+
mu24288Os23VtRf8kp57sj7+s+PSD/8+KiZiJ4sy5KrUUVijVQgfCpxPzpWWtQ/7
12+
JbjQMt+kZqJwKqdzXAY8osnljpYYvbNWnc0GZY09F6z95GqVgX/81Fe8W3Jz6I9w
13+
S2CXVneKGtux+6fztKbrA2b1kn69b3xClEHRLFZl9hKG8ck2H+gI5AEDgQmhTIXa
14+
pl85bPuh54uKiUGnedPk07biCw3ZE5GTGWzEq5qMqFEfb19/L1vOvgx/Q4aqmjJw
15+
lONB9DzMftetdKaR5SS+vH0QUhiWXwy7j1TjYtJP4M6fLinwguMYG8Qbg7NkL4QC
16+
9T7zR5CZPJ0Q/Npiwv7qdMzyL7QklZ9y3YeA5wceyc2/zh0INN5bf4J1mDZjhYH9
17+
CIgVHSj6z44rWq9L+OzYT0EMDhZO0OeakTWgqXNICfeEXZ5hy3QVCUvKrgmnqs0f
18+
imdH6dZQIGQIQ8Vcg/psk2hEP1hRWROn/cgCdadcEqbMdbtOUuMcnr0K6B/bVbXx
19+
jAV4eVcCcS3w3wIG4Ki2aIXnXrHyEJmZJb03Ko7VXP0NTGuGfPYQj2ox4a4wViOG
20+
pxxbnGGAFqV+BIVlhUMfL9PlatqsI6kUzJIsJUiyk6oPb3KeNQ5+MtS0S1DV0jA5
21+
wxDQZyEFiUsl6GLYSm4RajxoHdLR7Xqj3D7EWKGt/wIDAQABo1AwTjAMBgNVHRME
22+
BTADAQH/MB0GA1UdDgQWBBRvLFXv6sI+ePP5aegYUWoVHAfRzTAfBgNVHSMEGDAW
23+
gBRvLFXv6sI+ePP5aegYUWoVHAfRzTANBgkqhkiG9w0BAQsFAAOCAgEAK+QAtzhk
24+
ih8Tng9cOheswrbWf9pclMyfl38+NsJxsZnpa2SlBp3qJl0fymyNLLBfyeRUFr++
25+
x1cRAEwVv6R6Iepj252+U+Cmz48xIthF29JxoC+x2P2YDGyqVBm4uuw54EIF0r0H
26+
AvjTPSNa54gA3+KiK64ypFdlHZrwx3W9b5tUsfycpj2Jrn2HgTbWQD2gaYeIIdq6
27+
DNmPCJg6NQE9jlvNmVqlBavjc7MJqqd+0+XtCIWhaoqeu/T6g2Epth25cuqPKc0E
28+
rltKiXNiZHcDfFnu7B6kw2LVA6EQdf5GO9JtAaiwhRugp1dJ5rdQqdaYpJngZtvd
29+
8+PSdDZrXow0a1jW2w+3lM5XW3qtzIKJz4Q8CXL540s+SeRjLRwY02OZCvG4fC8c
30+
D57MIFKoReYy5LgBHdPGmx8Kexo7vk2ib9taQCSd6fh0Ol070pNiOnLP9lE9iEqq
31+
EvU1A+0dtPHbfyXqw9tdY18nxXbooypQZSqfxPSq3Bpv8KTsr9SSG+DV2LcJRfvi
32+
OfVTPeIWW8C8SkbEXaTCUVgaNeYqvFsfsvkTmfhO8GHglDgnsveXHfnAwlC2Uxdq
33+
T64oKToV7N1L2RA0JR9gJ4RQwPfyaFOHOPjd+3t4DFVl54GNbNfvELHRReoyJPse
34+
SZeL4h6T3L17FWzugHMjxFi4f1/nPNk7d5Y=
3535
-----END CERTIFICATE-----

tests/certs/ca.crl.pem

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN X509 CRL-----
2+
MIIDAjCB6wIBATANBgkqhkiG9w0BAQsFADCBoTELMAkGA1UEBhMCQ0ExEDAOBgNV
3+
BAgMB09udGFyaW8xEDAOBgNVBAcMB1Rvcm9udG8xGDAWBgNVBAoMD01hZ2ljU3Rh
4+
Y2sgSW5jLjEWMBQGA1UECwwNYXN5bmNwZyB0ZXN0czEdMBsGA1UEAwwUYXN5bmNw
5+
ZyB0ZXN0IHJvb3QgY2ExHTAbBgkqhkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvFw0y
6+
MTA5MTQxNjA2MDFaFw0yMTA5MTUxNjA2MDFaMBUwEwICEAAXDTIxMDkxNDE2MDYw
7+
MVowDQYJKoZIhvcNAQELBQADggIBAL4yfNmvGS8SkIVbRzdAC9+XJPw/dBJOUJwr
8+
EgERICAz7OTqG1PkmMhPL00Dm9fe52+KnSwHgL749W0S/X5rTNMSwLyGiiJ5HYbH
9+
GFRKQ/cvXLi4jYpSI1Ac94kk0japf3SfwEw3+122oba8SiAVP0nY3bHpHvNfOaDV
10+
fhbFTwb5bFm6ThqlKLZxGCKP0fGeQ4homuwgRiLE/UOiue5ted1ph0PkKVui208k
11+
FnhNYXSllakTGT8ZZZZVid/4tSHqJEY9vbdMXNv1GX8mhjoU1Gv9dOuyFGgUc9Vx
12+
e7gzf/Wf36vKI29o8QGkkTslRZpMG59z3sG4Y0vJEoqXMB6eQLOr5iUCyj2CyDha
13+
66pwrdc1fRt3EvNXUWkdHfY3EHb7DxueedDEgtmfSNbEaZTXa5RaZRavNGNTaPDf
14+
UcrDU4w1N0wkYLQxPqd+VPcf1iKyfkAydpeOq9CChqRD0Tx58eTn6N/lLGFPPRfs
15+
x47BA4FmefBeXZzd5HiXCUouk3qHIHs2yCzFs+TEBkx5eV42cP++HxjirPydLf6Y
16+
G/o/TKRnc/2Lw+dCzvUV/p3geuw4+vq1BIFanwB9jp4tGaBrffIAyle8vPQLw6bp
17+
1o1O39pdxniz+c9r0Kw/ETxTqRLbasSib5FHq5G/G9a+QxPsLAzKgwLWhR4fXvbu
18+
YPbhYhRP
19+
-----END X509 CRL-----

tests/certs/ca.key.pem

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
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
51+
-----END RSA PRIVATE KEY-----

tests/certs/client.key.protected.pem

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
Proc-Type: 4,ENCRYPTED
3+
DEK-Info: AES-256-CBC,B222CD7D00828606A07DBC489D400921
4+
5+
LRHsNGUsD5bG9+x/1UlzImN0rqEF10sFPBmxKeQpXQ/hy4iR+X/Gagoyagi23wOn
6+
EZf0sCLJx95ixG+4fXJDX0jgBtqeziVNS4FLWHIuf3+blja8nf4tkmmH9pF8jFQ0
7+
i1an3TP6KRyDKa17gioOdtSsS51BZmPkp3MByJQsrMhyB0txEUsGtUMaBTYmVN/5
8+
uYHf9MsmfcfQy30nt2t6St6W82QupHHMOx5xyhPJo8cqQncZC7Dwo4hyDV3h3vWn
9+
UjaRZiEMmQ3IgCwfJd1VmMECvrwXd/sTOXNhofWwDQIqmQ3GGWdrRnmgD863BQT3
10+
V8RVyPLkutOnrZ/kiMSAuiXGsSYK0TV8F9TaP/abLob4P8jbKYLcuR7ws3cu1xBl
11+
XWt9RALxGPUyHIy+BWLXJTYL8T+TVJpiKsAGCQB54j8VQBSArwFL4LnzdUu1txe2
12+
qa6ZEwt4q6SEwOTJpJWz3oJ1j+OTsRCN+4dlyo7sEZMeyTRp9nUzwulhd+fOdIhY
13+
2UllMG71opKfNxZzEW7lq6E/waf0MmxwjUJmgwVO218yag9oknHnoFwewF42DGY7
14+
072h23EJeKla7sI+MAB18z01z6C/yHWXLybOlXaGqk6zOm3OvTUFnUXtKzlBO2v3
15+
FQwrOE5U/VEyQkNWzHzh4j4LxYEL9/B08PxaveUwvNVGn9I3YknE6uMfcU7VuxDq
16+
+6bgM6r+ez+9QLFSjH/gQuPs2DKX0h3b9ppQNx+MANX0DEGbGabJiBp887f8pG6Q
17+
tW0i0+rfzYz3JwnwIuMZjYz6qUlP4bJMEmmDfod3fbnvg3MoCSMTUvi1Tq3Iiv4L
18+
GM5/YNkL0V3PhOI686aBfU7GLGXQFhdbQ9xrSoQRBmmNBqTCSf+iIEoTxlBac8GQ
19+
vSzDO+A+ovBP36K13Yn7gzuN/3PLZXH2TZ8t2b/OkEXOciH5KbycGHQA7gqxX1P4
20+
J55gpqPAWe8e7wKheWj3BMfmbWuH4rpiEkrLpqbTSfTwIKqplk253chmJj5I82XI
21+
ioFLS5vCi9JJsTrQ720O+VQPVB5xeA80WL8NxamWQb/KkvVnb4dTmaV30RCgLLZC
22+
tuMx8YSW71ALLT15qFB2zlMDKZO1jjunNE71BUFBPIkTKEOCyMAiF60fFeIWezxy
23+
kvBBOg7+MTcZNeW110FqRWNGr2A5KYFN15g+YVpfEoF26slHisSjVW5ndzGh0kaQ
24+
sIOjQitA9JYoLua7sHvsr6H5KdCGjNxv7O7y8wLGBVApRhU0wxZtbClqqEUvCLLP
25+
UiLDp9L34wDL7sGrfNgWA4UuN29XQzTxI5kbv/EPKhyt2oVHLqUiE+eGyvnuYm+X
26+
KqFi016nQaxTU5Kr8Pl0pSHbJMLFDWLSpsbbTB6YJpdEGxJoj3JB3VncOpwcuK+G
27+
xZ1tV2orPt1s/6m+/ihzRgoEkyLwcLRPN7ojgD/sqS679ZGf1IkDMgFCQe4g0UWm
28+
Fw7v816MNCgypUM5hQaU+Jp8vSlEc29RbrdSHbcxrKj/xPCLWrAbvmI5tgonKmuJ
29+
J1LW8AXyh/EUp/uUh++jqVGx+8pFfcmJw6V6JrJzQ7HMlakkry7N1eAGrIJGtYCW
30+
-----END RSA PRIVATE KEY-----

0 commit comments

Comments
 (0)