From 96281b0c423518463d7d5fb21f141f46af34bdc6 Mon Sep 17 00:00:00 2001 From: Fantix King Date: Mon, 13 Sep 2021 12:20:21 -0400 Subject: [PATCH 1/6] Fix SSL compatibility of libpq --- asyncpg/connect_utils.py | 155 +++++++++++++++++++++------ asyncpg/connection.py | 14 +++ tests/certs/client.key.protected.pem | 30 ++++++ tests/test_connect.py | 145 +++++++++++++++++-------- 4 files changed, 264 insertions(+), 80 deletions(-) create mode 100644 tests/certs/client.key.protected.pem diff --git a/asyncpg/connect_utils.py b/asyncpg/connect_utils.py index cd94b834..7ebcc0cd 100644 --- a/asyncpg/connect_utils.py +++ b/asyncpg/connect_utils.py @@ -18,6 +18,7 @@ import ssl as ssl_module import stat import struct +import sys import time import typing import urllib.parse @@ -220,13 +221,27 @@ def _parse_hostlist(hostlist, port, *, unquote=False): return hosts, port +def _parse_tls_version(tls_version): + if tls_version.startswith('SSL'): + raise ValueError( + f"Unsupported TLS version: {tls_version}" + ) + try: + return ssl_module.TLSVersion[tls_version.replace('.', '_')] + except KeyError: + raise ValueError( + f"No such TLS version: {tls_version}" + ) + + def _parse_connect_dsn_and_args(*, dsn, host, port, user, password, passfile, database, ssl, connect_timeout, server_settings): # `auth_hosts` is the version of host information for the purposes # of reading the pgpass file. auth_hosts = None - sslcert = sslkey = sslrootcert = sslcrl = None + sslcert = sslkey = sslrootcert = sslcrl = sslpassword = None + sslcompression = ssl_min_protocol_version = ssl_max_protocol_version = None if dsn: parsed = urllib.parse.urlparse(dsn) @@ -312,24 +327,32 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, ssl = val if 'sslcert' in query: - val = query.pop('sslcert') - if sslcert is None: - sslcert = val + sslcert = query.pop('sslcert') if 'sslkey' in query: - val = query.pop('sslkey') - if sslkey is None: - sslkey = val + sslkey = query.pop('sslkey') if 'sslrootcert' in query: - val = query.pop('sslrootcert') - if sslrootcert is None: - sslrootcert = val + sslrootcert = query.pop('sslrootcert') if 'sslcrl' in query: - val = query.pop('sslcrl') - if sslcrl is None: - sslcrl = val + sslcrl = query.pop('sslcrl') + + if 'sslpassword' in query: + sslpassword = query.pop('sslpassword') + + if 'sslcompression' in query: + sslcompression = query.pop('sslcompression') + + if 'ssl_min_protocol_version' in query: + ssl_min_protocol_version = query.pop( + 'ssl_min_protocol_version' + ) + + if 'ssl_max_protocol_version' in query: + ssl_max_protocol_version = query.pop( + 'ssl_max_protocol_version' + ) if query: if server_settings is None: @@ -451,34 +474,98 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, if sslmode < SSLMode.allow: ssl = False else: - ssl = ssl_module.create_default_context( - ssl_module.Purpose.SERVER_AUTH) + ssl = ssl_module.SSLContext(ssl_module.PROTOCOL_TLS_CLIENT) ssl.check_hostname = sslmode >= SSLMode.verify_full - ssl.verify_mode = ssl_module.CERT_REQUIRED - if sslmode <= SSLMode.require: + if sslmode < SSLMode.require: ssl.verify_mode = ssl_module.CERT_NONE + else: + if sslrootcert is None: + sslrootcert = os.getenv('PGSSLROOTCERT') + if sslrootcert: + ssl.load_verify_locations(cafile=sslrootcert) + ssl.verify_mode = ssl_module.CERT_REQUIRED + else: + sslrootcert = os.path.expanduser('~/.postgresql/root.crt') + try: + ssl.load_verify_locations(cafile=sslrootcert) + except FileNotFoundError: + if sslmode > SSLMode.require: + raise ValueError( + f'root certificate file "{sslrootcert}" does ' + f'not exist\nEither provide the file or ' + f'change sslmode to disable server ' + f'certificate verification.' + ) + elif sslmode == SSLMode.require: + ssl.verify_mode = ssl_module.CERT_NONE + else: + assert False, 'unreachable' + else: + ssl.verify_mode = ssl_module.CERT_REQUIRED - if sslcert is None: - sslcert = os.getenv('PGSSLCERT') + if sslcrl is None: + sslcrl = os.getenv('PGSSLCRL') + if sslcrl: + ssl.load_verify_locations(cafile=sslcrl) + ssl.verify_flags |= ssl_module.VERIFY_CRL_CHECK_CHAIN + else: + sslcrl = os.path.expanduser('~/.postgresql/root.crl') + try: + ssl.load_verify_locations(cafile=sslcrl) + except FileNotFoundError: + pass + else: + ssl.verify_flags |= ssl_module.VERIFY_CRL_CHECK_CHAIN if sslkey is None: sslkey = os.getenv('PGSSLKEY') - - if sslrootcert is None: - sslrootcert = os.getenv('PGSSLROOTCERT') - - if sslcrl is None: - sslcrl = os.getenv('PGSSLCRL') - + if not sslkey: + sslkey = os.path.expanduser('~/.postgresql/postgresql.key') + if not os.path.exists(sslkey): + sslkey = None + if not sslpassword: + sslpassword = '' + if sslcert is None: + sslcert = os.getenv('PGSSLCERT') if sslcert: - ssl.load_cert_chain(sslcert, keyfile=sslkey) - - if sslrootcert: - ssl.load_verify_locations(cafile=sslrootcert) - - if sslcrl: - ssl.load_verify_locations(cafile=sslcrl) - ssl.verify_flags |= ssl_module.VERIFY_CRL_CHECK_CHAIN + ssl.load_cert_chain( + sslcert, keyfile=sslkey, password=lambda: sslpassword + ) + else: + sslcert = os.path.expanduser('~/.postgresql/postgresql.crt') + try: + ssl.load_cert_chain( + sslcert, keyfile=sslkey, password=lambda: sslpassword + ) + except FileNotFoundError: + pass + + # OpenSSL 1.1.1 keylog file, copied from create_default_context() + if hasattr(ssl, 'keylog_filename'): + keylogfile = os.environ.get('SSLKEYLOGFILE') + if keylogfile and not sys.flags.ignore_environment: + ssl.keylog_filename = keylogfile + + if sslcompression is None: + sslcompression = os.getenv('PGSSLCOMPRESSION') + if sslcompression == '1': + ssl.options &= ~ssl_module.OP_NO_COMPRESSION + + if ssl_min_protocol_version is None: + ssl_min_protocol_version = os.getenv( + 'PGSSLMINPROTOCOLVERSION', 'TLSv1.2' + ) + if ssl_min_protocol_version: + ssl.minimum_version = _parse_tls_version( + ssl_min_protocol_version + ) + + if ssl_max_protocol_version is None: + ssl_max_protocol_version = os.getenv('PGSSLMAXPROTOCOLVERSION') + if ssl_max_protocol_version: + ssl.maximum_version = _parse_tls_version( + ssl_max_protocol_version + ) elif ssl is True: ssl = ssl_module.create_default_context() diff --git a/asyncpg/connection.py b/asyncpg/connection.py index 26249679..1c7ca801 100644 --- a/asyncpg/connection.py +++ b/asyncpg/connection.py @@ -2020,6 +2020,20 @@ async def connect(dsn=None, *, The ``sslcert``, ``sslkey``, ``sslrootcert``, and ``sslcrl`` options are supported in the *dsn* argument. + .. versionchanged:: 0.25.0 + The ``sslpassword``, ``sslcompression``, ``ssl_min_protocol_version``, + and ``ssl_max_protocol_version`` options are supported in the *dsn* + argument. + + .. versionchanged:: 0.25.0 + Default system root CA certificates won't be loaded when specifying a + particular sslmode, following the same behavior in libpq. + + .. versionchanged:: 0.25.0 + The ``sslcert``, ``sslkey``, ``sslrootcert``, and ``sslcrl`` options + in the *dsn* argument now have consistent default values of files under + ``~/.postgresql/`` as libpq. + .. _SSLContext: https://docs.python.org/3/library/ssl.html#ssl.SSLContext .. _create_default_context: https://docs.python.org/3/library/ssl.html#ssl.create_default_context diff --git a/tests/certs/client.key.protected.pem b/tests/certs/client.key.protected.pem new file mode 100644 index 00000000..0c5a9795 --- /dev/null +++ b/tests/certs/client.key.protected.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,B222CD7D00828606A07DBC489D400921 + +LRHsNGUsD5bG9+x/1UlzImN0rqEF10sFPBmxKeQpXQ/hy4iR+X/Gagoyagi23wOn +EZf0sCLJx95ixG+4fXJDX0jgBtqeziVNS4FLWHIuf3+blja8nf4tkmmH9pF8jFQ0 +i1an3TP6KRyDKa17gioOdtSsS51BZmPkp3MByJQsrMhyB0txEUsGtUMaBTYmVN/5 +uYHf9MsmfcfQy30nt2t6St6W82QupHHMOx5xyhPJo8cqQncZC7Dwo4hyDV3h3vWn +UjaRZiEMmQ3IgCwfJd1VmMECvrwXd/sTOXNhofWwDQIqmQ3GGWdrRnmgD863BQT3 +V8RVyPLkutOnrZ/kiMSAuiXGsSYK0TV8F9TaP/abLob4P8jbKYLcuR7ws3cu1xBl +XWt9RALxGPUyHIy+BWLXJTYL8T+TVJpiKsAGCQB54j8VQBSArwFL4LnzdUu1txe2 +qa6ZEwt4q6SEwOTJpJWz3oJ1j+OTsRCN+4dlyo7sEZMeyTRp9nUzwulhd+fOdIhY +2UllMG71opKfNxZzEW7lq6E/waf0MmxwjUJmgwVO218yag9oknHnoFwewF42DGY7 +072h23EJeKla7sI+MAB18z01z6C/yHWXLybOlXaGqk6zOm3OvTUFnUXtKzlBO2v3 +FQwrOE5U/VEyQkNWzHzh4j4LxYEL9/B08PxaveUwvNVGn9I3YknE6uMfcU7VuxDq ++6bgM6r+ez+9QLFSjH/gQuPs2DKX0h3b9ppQNx+MANX0DEGbGabJiBp887f8pG6Q +tW0i0+rfzYz3JwnwIuMZjYz6qUlP4bJMEmmDfod3fbnvg3MoCSMTUvi1Tq3Iiv4L +GM5/YNkL0V3PhOI686aBfU7GLGXQFhdbQ9xrSoQRBmmNBqTCSf+iIEoTxlBac8GQ +vSzDO+A+ovBP36K13Yn7gzuN/3PLZXH2TZ8t2b/OkEXOciH5KbycGHQA7gqxX1P4 +J55gpqPAWe8e7wKheWj3BMfmbWuH4rpiEkrLpqbTSfTwIKqplk253chmJj5I82XI +ioFLS5vCi9JJsTrQ720O+VQPVB5xeA80WL8NxamWQb/KkvVnb4dTmaV30RCgLLZC +tuMx8YSW71ALLT15qFB2zlMDKZO1jjunNE71BUFBPIkTKEOCyMAiF60fFeIWezxy +kvBBOg7+MTcZNeW110FqRWNGr2A5KYFN15g+YVpfEoF26slHisSjVW5ndzGh0kaQ +sIOjQitA9JYoLua7sHvsr6H5KdCGjNxv7O7y8wLGBVApRhU0wxZtbClqqEUvCLLP +UiLDp9L34wDL7sGrfNgWA4UuN29XQzTxI5kbv/EPKhyt2oVHLqUiE+eGyvnuYm+X +KqFi016nQaxTU5Kr8Pl0pSHbJMLFDWLSpsbbTB6YJpdEGxJoj3JB3VncOpwcuK+G +xZ1tV2orPt1s/6m+/ihzRgoEkyLwcLRPN7ojgD/sqS679ZGf1IkDMgFCQe4g0UWm +Fw7v816MNCgypUM5hQaU+Jp8vSlEc29RbrdSHbcxrKj/xPCLWrAbvmI5tgonKmuJ +J1LW8AXyh/EUp/uUh++jqVGx+8pFfcmJw6V6JrJzQ7HMlakkry7N1eAGrIJGtYCW +-----END RSA PRIVATE KEY----- diff --git a/tests/test_connect.py b/tests/test_connect.py index be694d67..b942aa33 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -10,11 +10,13 @@ import ipaddress import os import platform +import shutil import ssl import stat import tempfile import textwrap import unittest +import unittest.mock import urllib.parse import weakref @@ -37,6 +39,34 @@ CLIENT_CA_CERT_FILE = os.path.join(CERTS, 'client_ca.cert.pem') CLIENT_SSL_CERT_FILE = os.path.join(CERTS, 'client.cert.pem') CLIENT_SSL_KEY_FILE = os.path.join(CERTS, 'client.key.pem') +CLIENT_SSL_PROTECTED_KEY_FILE = os.path.join(CERTS, 'client.key.protected.pem') + + +@contextlib.contextmanager +def mock_dot_postgresql(*, ca=True, client=False, protected=False): + with tempfile.TemporaryDirectory() as temp_dir: + pg_home = os.path.join(temp_dir, '.postgresql') + os.mkdir(pg_home) + if ca: + shutil.copyfile( + SSL_CA_CERT_FILE, os.path.join(pg_home, 'root.crt') + ) + if client: + shutil.copyfile( + CLIENT_SSL_CERT_FILE, os.path.join(pg_home, 'postgresql.crt') + ) + if protected: + shutil.copyfile( + CLIENT_SSL_PROTECTED_KEY_FILE, + os.path.join(pg_home, 'postgresql.key'), + ) + else: + shutil.copyfile( + CLIENT_SSL_KEY_FILE, + os.path.join(pg_home, 'postgresql.key'), + ) + with unittest.mock.patch.dict('os.environ', {'HOME': temp_dir}): + yield class TestSettings(tb.ConnectedTestCase): @@ -1155,8 +1185,10 @@ async def verify_fails(sslmode): await verify_works('allow') await verify_works('prefer') await verify_fails('require') - await verify_fails('verify-ca') - await verify_fails('verify-full') + with mock_dot_postgresql(): + await verify_fails('require') + await verify_fails('verify-ca') + await verify_fails('verify-full') async def test_connection_implicit_host(self): conn_spec = self.get_connection_spec() @@ -1263,8 +1295,7 @@ async def verify_works(sslmode, *, host='localhost'): if con: await con.close() - async def verify_fails(sslmode, *, host='localhost', - exn_type=ssl.SSLError): + async def verify_fails(sslmode, *, host='localhost', exn_type): # XXX: uvloop artifact old_handler = self.loop.get_exception_handler() con = None @@ -1286,23 +1317,16 @@ async def verify_fails(sslmode, *, host='localhost', await verify_works('allow') await verify_works('prefer') await verify_works('require') - await verify_fails('verify-ca') - await verify_fails('verify-full') + await verify_fails('verify-ca', exn_type=ValueError) + await verify_fails('verify-full', exn_type=ValueError) - orig_create_default_context = ssl.create_default_context - try: - def custom_create_default_context(*args, **kwargs): - ctx = orig_create_default_context(*args, **kwargs) - ctx.load_verify_locations(cafile=SSL_CA_CERT_FILE) - return ctx - ssl.create_default_context = custom_create_default_context + with mock_dot_postgresql(): + await verify_works('require') await verify_works('verify-ca') await verify_works('verify-ca', host='127.0.0.1') await verify_works('verify-full') await verify_fails('verify-full', host='127.0.0.1', exn_type=ssl.CertificateError) - finally: - ssl.create_default_context = orig_create_default_context async def test_ssl_connection_default_context(self): # XXX: uvloop artifact @@ -1396,41 +1420,68 @@ async def test_ssl_connection_client_auth_fails_with_wrong_setup(self): ssl=ssl_context, ) - async def test_ssl_connection_client_auth_custom_context(self): - ssl_context = ssl.create_default_context( - ssl.Purpose.SERVER_AUTH, - cafile=SSL_CA_CERT_FILE, - ) - ssl_context.load_cert_chain( - CLIENT_SSL_CERT_FILE, - keyfile=CLIENT_SSL_KEY_FILE, - ) - - con = await self.connect( - host='localhost', - user='ssl_user', - ssl=ssl_context, - ) + async def _test_works(self, **conn_args): + con = await self.connect(**conn_args) try: self.assertEqual(await con.fetchval('SELECT 42'), 42) finally: await con.close() + async def test_ssl_connection_client_auth_custom_context(self): + for key_file in (CLIENT_SSL_KEY_FILE, CLIENT_SSL_PROTECTED_KEY_FILE): + ssl_context = ssl.create_default_context( + ssl.Purpose.SERVER_AUTH, + cafile=SSL_CA_CERT_FILE, + ) + ssl_context.load_cert_chain( + CLIENT_SSL_CERT_FILE, + keyfile=key_file, + password='secRet', + ) + await self._test_works( + host='localhost', + user='ssl_user', + ssl=ssl_context, + ) + async def test_ssl_connection_client_auth_dsn(self): - params = urllib.parse.urlencode({ + params = { 'sslrootcert': SSL_CA_CERT_FILE, 'sslcert': CLIENT_SSL_CERT_FILE, 'sslkey': CLIENT_SSL_KEY_FILE, 'sslmode': 'verify-full', - }) - dsn = 'postgres://ssl_user@localhost/postgres?' + params - con = await self.connect(dsn=dsn) - - try: - self.assertEqual(await con.fetchval('SELECT 42'), 42) - finally: - await con.close() + } + params_str = urllib.parse.urlencode(params) + dsn = 'postgres://ssl_user@localhost/postgres?' + params_str + await self._test_works(dsn=dsn) + + params['sslkey'] = CLIENT_SSL_PROTECTED_KEY_FILE + params['sslpassword'] = 'secRet' + params_str = urllib.parse.urlencode(params) + dsn = 'postgres://ssl_user@localhost/postgres?' + params_str + await self._test_works(dsn=dsn) + + async def test_ssl_connection_client_auth_env(self): + env = { + 'PGSSLROOTCERT': SSL_CA_CERT_FILE, + 'PGSSLCERT': CLIENT_SSL_CERT_FILE, + 'PGSSLKEY': CLIENT_SSL_KEY_FILE, + } + dsn = 'postgres://ssl_user@localhost/postgres?sslmode=verify-full' + with unittest.mock.patch.dict('os.environ', env): + await self._test_works(dsn=dsn) + + env['PGSSLKEY'] = CLIENT_SSL_PROTECTED_KEY_FILE + with unittest.mock.patch.dict('os.environ', env): + await self._test_works(dsn=dsn + '&sslpassword=secRet') + + async def test_ssl_connection_client_auth_dot_postgresql(self): + dsn = 'postgres://ssl_user@localhost/postgres?sslmode=verify-full' + with mock_dot_postgresql(client=True): + await self._test_works(dsn=dsn) + with mock_dot_postgresql(client=True, protected=True): + await self._test_works(dsn=dsn + '&sslpassword=secRet') @unittest.skipIf(os.environ.get('PGHOST'), 'unmanaged cluster') @@ -1460,14 +1511,15 @@ async def verify_works(sslmode, *, host='localhost'): if con: await con.close() - async def verify_fails(sslmode, *, host='localhost', - exn_type=ssl.SSLError): + async def verify_fails(sslmode, *, host='localhost'): # XXX: uvloop artifact old_handler = self.loop.get_exception_handler() con = None try: self.loop.set_exception_handler(lambda *args: None) - with self.assertRaises(exn_type): + with self.assertRaises( + asyncpg.InvalidAuthorizationSpecificationError + ): con = await self.connect( dsn='postgresql://foo/?sslmode=' + sslmode, host=host, @@ -1478,13 +1530,14 @@ async def verify_fails(sslmode, *, host='localhost', await con.close() self.loop.set_exception_handler(old_handler) - invalid_auth_err = asyncpg.InvalidAuthorizationSpecificationError await verify_works('disable') await verify_works('allow') await verify_works('prefer') - await verify_fails('require', exn_type=invalid_auth_err) - await verify_fails('verify-ca') - await verify_fails('verify-full') + await verify_fails('require') + with mock_dot_postgresql(): + await verify_fails('require') + await verify_fails('verify-ca') + await verify_fails('verify-full') async def test_nossl_connection_prefer_cancel(self): con = await self.connect( From 6e912fd66a1c3246eb648bfa01e1afa815a5ed1f Mon Sep 17 00:00:00 2001 From: Fantix King Date: Tue, 14 Sep 2021 16:22:19 -0400 Subject: [PATCH 2/6] Add CRL test --- tests/certs/ca.cert.pem | 66 ++++++------ tests/certs/ca.crl.pem | 19 ++++ tests/certs/ca.key.pem | 51 +++++++++ tests/certs/gen.py | 202 ++++++++++++++++++++++++++++++++++++ tests/certs/server.cert.pem | 63 ++++++----- tests/certs/server.key.pem | 98 ++++++++--------- tests/test_connect.py | 20 +++- 7 files changed, 404 insertions(+), 115 deletions(-) create mode 100644 tests/certs/ca.crl.pem create mode 100644 tests/certs/ca.key.pem create mode 100644 tests/certs/gen.py diff --git a/tests/certs/ca.cert.pem b/tests/certs/ca.cert.pem index 0329883d..4a8a7016 100644 --- a/tests/certs/ca.cert.pem +++ b/tests/certs/ca.cert.pem @@ -1,35 +1,35 @@ -----BEGIN CERTIFICATE----- -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 -ztcZLWC9GiyC/TFSJ1hDylkvvwcCX6PD7fLu +MIIGFjCCA/6gAwIBAgIIDAM+rFY5KqgwDQYJKoZIhvcNAQELBQAwgaExCzAJBgNV +BAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdUb3JvbnRvMRgwFgYD +VQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5jcGcgdGVzdHMxHTAb +BgNVBAMMFGFzeW5jcGcgdGVzdCByb290IGNhMR0wGwYJKoZIhvcNAQkBFg5oZWxs +b0BtYWdpYy5pbzAeFw0yMTA5MTMxNjA2MDFaFw00MDExMTMxNjA2MDFaMIGhMQsw +CQYDVQQGEwJDQTEQMA4GA1UECAwHT250YXJpbzEQMA4GA1UEBwwHVG9yb250bzEY +MBYGA1UECgwPTWFnaWNTdGFjayBJbmMuMRYwFAYDVQQLDA1hc3luY3BnIHRlc3Rz +MR0wGwYDVQQDDBRhc3luY3BnIHRlc3Qgcm9vdCBjYTEdMBsGCSqGSIb3DQEJARYO +aGVsbG9AbWFnaWMuaW8wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDK +mu24288Os23VtRf8kp57sj7+s+PSD/8+KiZiJ4sy5KrUUVijVQgfCpxPzpWWtQ/7 +JbjQMt+kZqJwKqdzXAY8osnljpYYvbNWnc0GZY09F6z95GqVgX/81Fe8W3Jz6I9w +S2CXVneKGtux+6fztKbrA2b1kn69b3xClEHRLFZl9hKG8ck2H+gI5AEDgQmhTIXa +pl85bPuh54uKiUGnedPk07biCw3ZE5GTGWzEq5qMqFEfb19/L1vOvgx/Q4aqmjJw +lONB9DzMftetdKaR5SS+vH0QUhiWXwy7j1TjYtJP4M6fLinwguMYG8Qbg7NkL4QC +9T7zR5CZPJ0Q/Npiwv7qdMzyL7QklZ9y3YeA5wceyc2/zh0INN5bf4J1mDZjhYH9 +CIgVHSj6z44rWq9L+OzYT0EMDhZO0OeakTWgqXNICfeEXZ5hy3QVCUvKrgmnqs0f +imdH6dZQIGQIQ8Vcg/psk2hEP1hRWROn/cgCdadcEqbMdbtOUuMcnr0K6B/bVbXx +jAV4eVcCcS3w3wIG4Ki2aIXnXrHyEJmZJb03Ko7VXP0NTGuGfPYQj2ox4a4wViOG +pxxbnGGAFqV+BIVlhUMfL9PlatqsI6kUzJIsJUiyk6oPb3KeNQ5+MtS0S1DV0jA5 +wxDQZyEFiUsl6GLYSm4RajxoHdLR7Xqj3D7EWKGt/wIDAQABo1AwTjAMBgNVHRME +BTADAQH/MB0GA1UdDgQWBBRvLFXv6sI+ePP5aegYUWoVHAfRzTAfBgNVHSMEGDAW +gBRvLFXv6sI+ePP5aegYUWoVHAfRzTANBgkqhkiG9w0BAQsFAAOCAgEAK+QAtzhk +ih8Tng9cOheswrbWf9pclMyfl38+NsJxsZnpa2SlBp3qJl0fymyNLLBfyeRUFr++ +x1cRAEwVv6R6Iepj252+U+Cmz48xIthF29JxoC+x2P2YDGyqVBm4uuw54EIF0r0H +AvjTPSNa54gA3+KiK64ypFdlHZrwx3W9b5tUsfycpj2Jrn2HgTbWQD2gaYeIIdq6 +DNmPCJg6NQE9jlvNmVqlBavjc7MJqqd+0+XtCIWhaoqeu/T6g2Epth25cuqPKc0E +rltKiXNiZHcDfFnu7B6kw2LVA6EQdf5GO9JtAaiwhRugp1dJ5rdQqdaYpJngZtvd +8+PSdDZrXow0a1jW2w+3lM5XW3qtzIKJz4Q8CXL540s+SeRjLRwY02OZCvG4fC8c +D57MIFKoReYy5LgBHdPGmx8Kexo7vk2ib9taQCSd6fh0Ol070pNiOnLP9lE9iEqq +EvU1A+0dtPHbfyXqw9tdY18nxXbooypQZSqfxPSq3Bpv8KTsr9SSG+DV2LcJRfvi +OfVTPeIWW8C8SkbEXaTCUVgaNeYqvFsfsvkTmfhO8GHglDgnsveXHfnAwlC2Uxdq +T64oKToV7N1L2RA0JR9gJ4RQwPfyaFOHOPjd+3t4DFVl54GNbNfvELHRReoyJPse +SZeL4h6T3L17FWzugHMjxFi4f1/nPNk7d5Y= -----END CERTIFICATE----- diff --git a/tests/certs/ca.crl.pem b/tests/certs/ca.crl.pem new file mode 100644 index 00000000..b5eb1d2e --- /dev/null +++ b/tests/certs/ca.crl.pem @@ -0,0 +1,19 @@ +-----BEGIN X509 CRL----- +MIIDAjCB6wIBATANBgkqhkiG9w0BAQsFADCBoTELMAkGA1UEBhMCQ0ExEDAOBgNV +BAgMB09udGFyaW8xEDAOBgNVBAcMB1Rvcm9udG8xGDAWBgNVBAoMD01hZ2ljU3Rh +Y2sgSW5jLjEWMBQGA1UECwwNYXN5bmNwZyB0ZXN0czEdMBsGA1UEAwwUYXN5bmNw +ZyB0ZXN0IHJvb3QgY2ExHTAbBgkqhkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvFw0y +MTA5MTQxNjA2MDFaFw0yMTA5MTUxNjA2MDFaMBUwEwICEAAXDTIxMDkxNDE2MDYw +MVowDQYJKoZIhvcNAQELBQADggIBAL4yfNmvGS8SkIVbRzdAC9+XJPw/dBJOUJwr +EgERICAz7OTqG1PkmMhPL00Dm9fe52+KnSwHgL749W0S/X5rTNMSwLyGiiJ5HYbH +GFRKQ/cvXLi4jYpSI1Ac94kk0japf3SfwEw3+122oba8SiAVP0nY3bHpHvNfOaDV +fhbFTwb5bFm6ThqlKLZxGCKP0fGeQ4homuwgRiLE/UOiue5ted1ph0PkKVui208k +FnhNYXSllakTGT8ZZZZVid/4tSHqJEY9vbdMXNv1GX8mhjoU1Gv9dOuyFGgUc9Vx +e7gzf/Wf36vKI29o8QGkkTslRZpMG59z3sG4Y0vJEoqXMB6eQLOr5iUCyj2CyDha +66pwrdc1fRt3EvNXUWkdHfY3EHb7DxueedDEgtmfSNbEaZTXa5RaZRavNGNTaPDf +UcrDU4w1N0wkYLQxPqd+VPcf1iKyfkAydpeOq9CChqRD0Tx58eTn6N/lLGFPPRfs +x47BA4FmefBeXZzd5HiXCUouk3qHIHs2yCzFs+TEBkx5eV42cP++HxjirPydLf6Y +G/o/TKRnc/2Lw+dCzvUV/p3geuw4+vq1BIFanwB9jp4tGaBrffIAyle8vPQLw6bp +1o1O39pdxniz+c9r0Kw/ETxTqRLbasSib5FHq5G/G9a+QxPsLAzKgwLWhR4fXvbu +YPbhYhRP +-----END X509 CRL----- diff --git a/tests/certs/ca.key.pem b/tests/certs/ca.key.pem new file mode 100644 index 00000000..2d73448f --- /dev/null +++ b/tests/certs/ca.key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/tests/certs/gen.py b/tests/certs/gen.py new file mode 100644 index 00000000..c08f3061 --- /dev/null +++ b/tests/certs/gen.py @@ -0,0 +1,202 @@ +import datetime +import os + +from cryptography import x509 +from cryptography.hazmat import backends +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.x509 import oid + + +def _new_cert(issuer=None, is_issuer=False, serial_number=None, **subject): + backend = backends.default_backend() + private_key = rsa.generate_private_key( + public_exponent=65537, key_size=4096, backend=backend + ) + public_key = private_key.public_key() + subject = x509.Name( + [ + x509.NameAttribute(getattr(oid.NameOID, key.upper()), value) + for key, value in subject.items() + ] + ) + builder = ( + x509.CertificateBuilder() + .subject_name(subject) + .public_key(public_key) + .serial_number(serial_number or int.from_bytes(os.urandom(8), "big")) + ) + if issuer: + issuer_cert, signing_key = issuer + builder = ( + builder.issuer_name(issuer_cert.subject) + .not_valid_before(issuer_cert.not_valid_before) + .not_valid_after(issuer_cert.not_valid_after) + ) + aki_ext = x509.AuthorityKeyIdentifier( + key_identifier=issuer_cert.extensions.get_extension_for_class( + x509.SubjectKeyIdentifier + ).value.digest, + authority_cert_issuer=[x509.DirectoryName(issuer_cert.subject)], + authority_cert_serial_number=issuer_cert.serial_number, + ) + else: + signing_key = private_key + builder = ( + builder.issuer_name(subject) + .not_valid_before( + datetime.datetime.today() - datetime.timedelta(days=1) + ) + .not_valid_after( + datetime.datetime.today() + datetime.timedelta(weeks=1000) + ) + ) + aki_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key( + public_key + ) + if is_issuer: + builder = ( + builder.add_extension( + x509.BasicConstraints(ca=True, path_length=None), + critical=False, + ) + .add_extension( + x509.SubjectKeyIdentifier.from_public_key(public_key), + critical=False, + ) + .add_extension( + aki_ext, + critical=False, + ) + ) + else: + builder = ( + builder.add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=False, + key_encipherment=True, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False, + ), + critical=False, + ) + .add_extension( + x509.BasicConstraints(ca=False, path_length=None), + critical=False, + ) + .add_extension( + x509.ExtendedKeyUsage([oid.ExtendedKeyUsageOID.SERVER_AUTH]), + critical=False, + ) + .add_extension( + x509.SubjectAlternativeName([x509.DNSName("localhost")]), + critical=False, + ) + .add_extension( + x509.SubjectKeyIdentifier.from_public_key(public_key), + critical=False, + ) + .add_extension( + aki_ext, + critical=False, + ) + ) + certificate = builder.sign( + private_key=signing_key, + algorithm=hashes.SHA256(), + backend=backend, + ) + return certificate, private_key + + +def _write_cert(path, cert_key_pair, password=None): + certificate, private_key = cert_key_pair + if password: + encryption = serialization.BestAvailableEncryption(password) + else: + encryption = serialization.NoEncryption() + with open(path + ".key.pem", "wb") as f: + f.write( + private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=encryption, + ) + ) + with open(path + ".cert.pem", "wb") as f: + f.write( + certificate.public_bytes( + encoding=serialization.Encoding.PEM, + ) + ) + + +def new_ca(path, **subject): + cert_key_pair = _new_cert(is_issuer=True, **subject) + _write_cert(path, cert_key_pair) + return cert_key_pair + + +def new_cert( + path, ca_cert_key_pair, password=None, is_issuer=False, **subject +): + cert_key_pair = _new_cert( + issuer=ca_cert_key_pair, is_issuer=is_issuer, **subject + ) + _write_cert(path, cert_key_pair, password) + return cert_key_pair + + +def new_crl(path, issuer, cert): + issuer_cert, signing_key = issuer + revoked_cert = ( + x509.RevokedCertificateBuilder() + .serial_number(cert[0].serial_number) + .revocation_date(datetime.datetime.today()) + .build() + ) + builder = ( + x509.CertificateRevocationListBuilder() + .issuer_name(issuer_cert.subject) + .last_update(datetime.datetime.today()) + .next_update(datetime.datetime.today() + datetime.timedelta(days=1)) + .add_revoked_certificate(revoked_cert) + ) + crl = builder.sign(private_key=signing_key, algorithm=hashes.SHA256()) + with open(path + ".crl.pem", "wb") as f: + f.write(crl.public_bytes(encoding=serialization.Encoding.PEM)) + + +def main(): + ca = new_ca( + "ca", + country_name="CA", + state_or_province_name="Ontario", + locality_name="Toronto", + organization_name="MagicStack Inc.", + organizational_unit_name="asyncpg tests", + common_name="asyncpg test root ca", + email_address="hello@magic.io", + ) + server = new_cert( + "server", + ca, + country_name="CA", + state_or_province_name="Ontario", + organization_name="MagicStack Inc.", + organizational_unit_name="asyncpg tests", + common_name="localhost", + email_address="hello@magic.io", + serial_number=4096, + ) + new_crl('server', ca, server) + + +if __name__ == "__main__": + main() diff --git a/tests/certs/server.cert.pem b/tests/certs/server.cert.pem index ce8bf0f4..a4678151 100644 --- a/tests/certs/server.cert.pem +++ b/tests/certs/server.cert.pem @@ -1,40 +1,39 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- diff --git a/tests/certs/server.key.pem b/tests/certs/server.key.pem index d802fb3b..9c69c46c 100644 --- a/tests/certs/server.key.pem +++ b/tests/certs/server.key.pem @@ -1,51 +1,51 @@ -----BEGIN RSA PRIVATE KEY----- -MIIJKgIBAAKCAgEA+0WH9PX4a6Tsnp7xtUbZ51c77aqVagdfj9xYJPqD3X7u2Odf -yyYivZ91DiS23acfLOEQOfBNn2ZFcrLaXy33UAXo1VcvCsKNJY4FfS9A5OBZ4UTL -peagrTnZuRS4KMadg0V9jb5au6+s7jExPty9c+nZ59Kd6IbkPn31l9K5rj4/2WvG -pIj9k5YaXswJVBiTWGKxP9a3xMb9CG9bqNCD5kXo+1K2oDJyGE3mj6QSjlnFw6NN -f+dCOGWSs7JHMNZVVtRG2qsEIssZgpHseu9he684ZqdqrMCG6wBDW58sUBp6Dt6z -jyTLefs8ht0tT+ZcmPno2G3mgs1bLyQsQB8a7fqzzaW6wPwdZJBGO/qI7Zr/30VD -I7InLmxbg62tdrTP4CibXWfe6Qoi6xSNZd7FvP2OoCA7Nk6HahdwDocInB9fWV2j -jkqyeIdDSd9QUItCUSgyVm+XefO/T8B75PNCykyWAMMDGOBE706KZh4oXeMORoYp -LxsbtL0/7n/JPwQDHeLQHHRjiw2ydxH2/940jngnL1YCqWiUq06FPvl3zn+Qgim+ -kIhfJeYuQ8zxdh8P7Ay4i5neuum+FQZspPiSzx6jMQIOu+e+iBP2AIdu/UQK+JPU -epE2Pt5aEyuzgNEbg0cR6tQ3rJCbj0DdtU26ale5EeD8y1JYCXEYkED88bMCAwEA -AQKCAgEAtof0E8b7B3dvTGs6Ou2VLbD5H9VjZPqmOONgRLyXPjgPWhH6TKEPa6CC -cBvLm4jj5L46A1zFhp3MpV23tJy3o7InSZNj4PUjg7x/0EibY6h2omZPadz3q97y -grjCbxyZH9tDMcyuLNmZTg7+LyQ7nBCs8vLVMy2KcLsfxYKW0DT4PQFF9BBv5N6N -mX+u5yBTKUnIaQ+Zv6Ct/4qlkySmLIlsjeWwNP9wUqeEbaRKto4QU+Y1Tky4li9z -OoavoJKSu9jI/+BryLqxdWB74XIz5p2K40eK/qN9Xwl55PzkO+x/7n1pAvs/tQUF -GxNg70Hw0k/5DgAIC80SCFTGsG3oKLgPm1BS7Njoz8xcQvtZrYOKfEg/NOjUAWTE -SvXoLRqTQ4bUS6F6VgSA+qEEXrKFGt+ViddXrfuyXow5ZXjstgwuuZSzjLTM9LPF -tKEeB+hYbjpg0C7KuRGG5MfQ6eY8TjB3JCBGSBPw/4gv4DzkRoI2e2Qvgon6pNUT -ZiQMmuQHX3d+5QQErzUgAYF401DBi+9kG6e78hZ5uG3lTUOW372jcAkdkD/DdC1B -GMt7esIoyrO/57gFQXaFIQjSneWPiaxtYxUqpjbc0lCIfwYr3QFYzumZwUErJljl -CxDJ2ejW6ONUXDPRbzprHFDi0y71G7WRT7ZmwoQY/q/Yxwg3mAECggEBAP8+cgZl -001Np3M78KUYuUhDt+6J+ZujJnpCdsWqf0H0cxIA/FL6zpnyYP7nkum/QphE9CST -jFew1/JnuCtHHzE9BryChjL+kjXswFAhGncsP+UjPI1AEliFhPHIfBF25N3LYBvU -IO4syLLUsJsWlAaUbXBD29bSRUYwNPkspmblluZaKdS5fJQR9tiEUkNlPeUcjaMl -Mhblo4r3lZYMkJqm11DGlNUnXb5/kCMq0D+kvhVvoHfRqk5G30m9Yu0QSR6dVlgi -HiPXodNJSz0BZpfM/FXdnhAYnIANgPZS/St9JjpFeDTvo6vZgF9be+Tt29Zm7gxZ -4hvoPCUwE5LhKjMCggEBAPwEEssXK3qkrIYRe1gJjxAkpTHWj5w18QNRRsDvNIT3 -Ode2MtloeOl2csIRtvVZWEHWuFiQyvDbVXQvmoeJT+K1acw0mOOkXLME2BLOHTkJ -bYU5zd+dnF3W3CaOUagpesP7QZYiqdso88EugFDt5KtonFRtp/YNY4HxmEahE2I2 -KGVN6rFV5WIZsJyXhNCvacci1ZnJqwN/43Vx5ejsqjtypi1XAKlYzGj0ktDbOFGR -vZQdR5Q8rYQ+V7Bypwzbchq9+Udh3Xd8VmosADE0OoATDU6m1SHvsZMxZ83vcs/8 -pkwtzMlzo3q/yPSG+jTU7kq0PE8z628ol5sFZrFMmoECggEATQpHFmFDnvCSWzi7 -UMmemw49hRVGLtDWu042VUE5+elTlhqQDme/Vj4PQsEY2c6txhIB8sxKLumktHjT -4NQtuQnnb5yh7uBhtz8HaOgk+dV0T7AMBcJSBz/9uZC+yfKt77gEAUJM0jbYOQnz -aEwvT7EbOyhwQW3kFORWCOOOMj6YBl0uhRObY4HslLuTrN3xCadNpPGEJd8YNsi1 -8L1IJDW5hZr6rz+bjvUnx0WT57HM4eF4eNHi6o9/s90i79TbjQ8GUcGygTUDlido -OziiA62OeEhU/hy/l/L7et3fpnG2yR3Qw4GVUDhtA9s0EQwuL4+PyFCU68Fz7fGN -5uZpewKCAQEAvBAmHhwaPBlrDVk6XEY11mwiQoDFBmNSiZE7ZXqcDKWZKpoyc/78 -S+wyUxR5HbognHEpfB4A86AZsuxbOs2DKcELRHHzrdzXuFfjDpV1RTz91699LGQn -bfeKrdMCqKTbkiiLlwgjDQMQc5bJ9pqwTCFyl6aE8p6nJS8u3XYSSvXzSzXL764T -0RMusox3dmuQWiRqlarizWfAS8JFOX5ywo4Z6DfGrJkxYRkx/l25N1W0zTTUV5C4 -Q7lqIqhMdNHF4qLlxRkI9cN5kR1ov08kYLLW+VySLBL8xsTVm94WJZN6XdrHuYVr -94vq4F9hk89aS7EYWFp8VKVMDUkIi0KJAQKCAQEAmt1zJ9MP2xDIbNCY1Kuk3Zoy -oYcCqijK6i/9Aeu+9w8U1hSrcU5SOF4VQbDwB00RzzDPL7K4e77GulEDnanKnEpX -eu4lYuhCgG/G7uECU8jLOUQUVp8c4Fcyp29T0pTkow15TLifUOXAfQGfe8jK/SvI -jpAAwxBDwQ4HNGA3y3HOzmIt5riRLGahASxDpyTDBmFiuRPwyXNxEoO6ZMtaSL9t -ThhMc74EU8qFBBnzfaKkUZshB9jkcpQq800M99Wj5t31A4mNwz1tmAEM/Wvvbhea -Yx2I+nS6CQhg0DMAxGqalTTLWxjY4NK+j6Mb5FVpXGJ5yUef2TWVRUymm5XlSA== +MIIJKQIBAAKCAgEAwvenCzhPXe+m+QEOdqK1YRnhKKGAeRo0oV7BfDAwhrgrnc2R +kGg+T5liQYh3ddj13LHPdLehhVz4B1tNkfZPLSeMDwjU8sNRWkdiAI3ZHRmVIVOh +Ru4BRzI4WqdZpa5cImlFaUjtHa/+w7ekHnllwodpbjH4Vgs9LWQiH8CdTVpj2clq +H78ZShlRvLyjo6OMQ6fbxAFtcYDGHwhR7JZ4VeCBm40O0Fl/c0ckmOtoYd1BTYX9 +RgIzTt0oV6ZiUH/SKRdYyb9GPUlfm0URK5j5MZPn10riACnaNEHytEREQEkpHWiD +RPcmlRCJarg4zhObuI5f6kUX9R1XrIKY4SAyDKzoSdxRFgYEWN6HyfylakU5LFnE +4ZAgihbzuFG4fGOf88F+KqaC6yvz/mvgxB8IPSDaILE37gGuJUTGhDGkKAVIB5Xb +WWR6e4VJcnmveu1z5+M6jwTR2+61y14h3WfACZLbAdPW1ivr6kjbaXlN658NEA1G +I/5eY7kVFAapoGdLOWlI7iXLGHrORLL7l2nh7+cYnHGPT3e5WHJZ67a0Jvtv0K/5 +dBgs2gwB+6FcXe2foKAmQ3/B5rAmshtb/0Ya4wRCglGxXgQQFCZseT5TAJhhHwbB +yqVFOgzvYSFw7gXQcfxfxf0LoUYK2O7WwqDJyargkIMDTZfaL+7ht6pfSmkCAwEA +AQKCAgAujTM1WpyYsUAM9FOfv/nO1X8NVIJ4Z+lpHlbUcC0l/ZNsekjnUfyOxPDQ +9OSRHtyVdV8zXyUR0sDmAMbkswr0nRyz+kfeLwSdqa2ctEHC0PjqnC1F4k4r0bHi +81JUXO1iyf/ow6DaFcuer5pgLFw/tlVWGlhRMx3IWMBNFJB6h7qPpafRLK+9IY6C +ogfwanxzKwEuK6kWEMk9X58v/j19Q72uhl+jH7tuqu3yFUM3Gr0c5YEz1hKqIeQg +CXov/lUPuqNYiHMc7wgE6tjOsBfP3qDcpuSPZW7US2rH4ATr1IwcmXe+X8S2ktw8 +vv/RNJ1Z06TTKuwtenQUnJokJqvMMESqEHdld5wwDo3MxCqvkcSUeS22cKlBZjeF +8/5wqpTMVpWxE7kfZFsMinBIV3gRPh8v87aDjrULJYltLQ6e8Pd0sAO0x0jAby8H +o5mjPSjHsK0m4vJyNB0paiWJcbRMQXpKX7U3smXxxAqWaqRgkkXk6wGICxX2oV34 +T6tvQ7GPCqNR8wnnXDx07imcHGAMeT62Zo15DrupP7eRxtIaO+f94HQiM6aIIcDv +kXyNZP0B1THj1C9eFy2hy6yvVOv1ZTtaXSXCOcY5dstDDKKZiAs2JTgcMtT5AZ7H +Q0JZAulk2AIeLHlNktUOZeYAA7nrJVS+PhsPcOep5N9CeM2EgQKCAQEA5P/I9jv2 +ZLfzTLJq3h9t7HMPGC/u+a7xD6ycTA4h5LAaUoMibkMobG7Sm/idNwvjUK3eobpz +KV01L5B69Om0GlaCn1zyDvPiZ+Z6COqwlHMIQcNR8ohNyGMIzkngJPh8RJN5a1a+ +NkT+lAsxAZx4RUWOs+PboTrqy3YUWQZLbTK5k0nBoAwW6V7PmdrjDAz4AU3nabQ4 +9JXacMd1gzB7/VWFt3rprR39yfmTrT8vR1w/DRnWmYpIx/DZ1MDvkIeWdrzFakyu +ah8HkW+tFB2BajnXfD+GD/L2sdEhez9YVjv/enJrNrsPRRk6yJoUTydkqPejBOOz +DJTfdQknWBnFKwKCAQEA2fSjGu8SS/Ah3DVAdhczX8YDHfJm1ctY5/uGG5TQI8Fn +pN6jBt8e7a5Aqb32naO73xYKQipiJ30Diqp5bPXv/4SJB17qCJvvoIlWXmy6ROnC +a7ndAR7N6VgJHf7uvuPa9CCZQkpP2fG3MPJXAfynVPY+D/xonZBV/o9lioBGEin+ +ENqVYjb7tX7h0Of/IbCzbTMnmEiCaz3Mm/8RME9Mh8BZfbJTUk9Sb/Q6oTMwMd9H +GcsZj4XYbxYGdHA28mFlZoIUdDesd8ZUWka21U6GVdz4OJtfoI7MJdqRzt7uEwJC +UixWWQn+LFpNFjKjKnhFFc4re52MvKB90R+kWErMuwKCAQAp2ZkXbwPrijabmKux +JltHcAudJv1sgg0qCSiTOa32Bjz5QV/keL+FeYdh28CXk8OaNfxO4C01rQQQSj4+ +TguNGKxMhYbzNpz00gkRYhqdNpyWsCRkx4Y3jenJEXU2LHdBbRYuiK7AakGAOr9d +BQRx3HFk7Mpxn7vTLSQw1NaqATAq+7q4Dh2Nzrbv7jG6PRCB5IPbLIWQJWbDX6BZ +Nl4igSOr0XmtGqML62GSss5oIzKeqU8vxjbg22Jj4FKnvi/ASWVmtNbXLA6NBLTD +zVSeXi3EVjOg7I0rGAYfaQcy00owTYLMgMkcnqzAhnAZuyBJROB0/0v0i6x+zgpz +rln7AoIBAQCHK1TMK2ApgC8/pjboBdNynhbox6BHDumAVVkCWfQLeLKSaRCp/k3s +EZlAq/L6KMUmwUBzcF2XJ8y+fqL3lD7XNJbW32I9HJgr84CA5uVOP7q3nHkXbMc+ +474jwCrIb/8mT+E8X2HORD3cOS8EqHAOHPi4aU1oCk+Ko9vRXWQXd7t9MFJcqsTH +9nyNVpO/jRp5qrPvmWhoodb3F+TNFSDdP8lATwuljFQP4mNJ/bjx9QrfUDn17Igh +vIMcS0uIXibIv/t3Z9+qGHHP2vMgrqZZMcUvNgzEQksRXs/2gAMd/tSqqZyTc8MS +Np6AGb9fY19U+pu0+iyB/vaIbxs5NoppAoIBAQCdpwKUiaGERg7f6I8f3fy+vvYQ +RyeNbizFKSEBwyE0b9izImUuKtlOqpK2XbuFZkEXVJ8juWU/7YurMIsBdosFegPu +qxtLEq2AOBtxxRWsLWZAaaesLh6MS0YJ6YjibuK1ITfiKInIkXdc65TQ6BXXsZme +4tQmnCY+C70iG5Xnt6ImH0/FEgnyBbbTHYvFqPTxDFy5Xu0cbtRgEu6rFK5GoYur +35BGoV1tYa50y3dHR79cDYp5sPM/qZ9teEnV++dQKCRJ4oOcGsYBHqc6tEjCLWpv +ji6ZAgx0TbI3oQtECNdpT2cSvYRdSrKQth7fPVo/FhLMrmc6d18cnZswXNYQ -----END RSA PRIVATE KEY----- diff --git a/tests/test_connect.py b/tests/test_connect.py index b942aa33..4052e6ef 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -34,6 +34,7 @@ CERTS = os.path.join(os.path.dirname(__file__), 'certs') SSL_CA_CERT_FILE = os.path.join(CERTS, 'ca.cert.pem') +SSL_CA_CRL_FILE = os.path.join(CERTS, 'ca.crl.pem') SSL_CERT_FILE = os.path.join(CERTS, 'server.cert.pem') SSL_KEY_FILE = os.path.join(CERTS, 'server.key.pem') CLIENT_CA_CERT_FILE = os.path.join(CERTS, 'client_ca.cert.pem') @@ -43,7 +44,7 @@ @contextlib.contextmanager -def mock_dot_postgresql(*, ca=True, client=False, protected=False): +def mock_dot_postgresql(*, ca=True, crl=False, client=False, protected=False): with tempfile.TemporaryDirectory() as temp_dir: pg_home = os.path.join(temp_dir, '.postgresql') os.mkdir(pg_home) @@ -51,6 +52,10 @@ def mock_dot_postgresql(*, ca=True, client=False, protected=False): shutil.copyfile( SSL_CA_CERT_FILE, os.path.join(pg_home, 'root.crt') ) + if crl: + shutil.copyfile( + SSL_CA_CRL_FILE, os.path.join(pg_home, 'root.crl') + ) if client: shutil.copyfile( CLIENT_SSL_CERT_FILE, os.path.join(pg_home, 'postgresql.crt') @@ -1328,6 +1333,19 @@ async def verify_fails(sslmode, *, host='localhost', exn_type): await verify_fails('verify-full', host='127.0.0.1', exn_type=ssl.CertificateError) + with mock_dot_postgresql(crl=True): + await verify_fails('disable', exn_type=invalid_auth_err) + await verify_works('allow') + await verify_works('prefer') + await verify_fails('require', + exn_type=ssl.CertificateError) + await verify_fails('verify-ca', + exn_type=ssl.CertificateError) + await verify_fails('verify-ca', host='127.0.0.1', + exn_type=ssl.CertificateError) + await verify_fails('verify-full', + exn_type=ssl.CertificateError) + async def test_ssl_connection_default_context(self): # XXX: uvloop artifact old_handler = self.loop.get_exception_handler() From 3ffdde5d9318992cc6b2bf6e9c09329a51f4234f Mon Sep 17 00:00:00 2001 From: Fantix King Date: Tue, 14 Sep 2021 16:56:22 -0400 Subject: [PATCH 3/6] Add TLS version test --- tests/test_connect.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/test_connect.py b/tests/test_connect.py index 4052e6ef..d69fdcaf 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -1213,6 +1213,8 @@ def get_server_settings(cls): 'ssl_cert_file': SSL_CERT_FILE, 'ssl_key_file': SSL_KEY_FILE, 'ssl_ca_file': CLIENT_CA_CERT_FILE, + 'ssl_min_protocol_version': 'TLSv1.2', + 'ssl_max_protocol_version': 'TLSv1.2', }) return conf @@ -1408,6 +1410,42 @@ async def test_executemany_uvloop_ssl_issue_700(self): finally: await con.close() + async def test_tls_version(self): + # XXX: uvloop artifact + old_handler = self.loop.get_exception_handler() + try: + self.loop.set_exception_handler(lambda *args: None) + with self.assertRaisesRegex(ssl.SSLError, 'protocol version'): + await self.connect( + dsn='postgresql://ssl_user@localhost/postgres' + '?sslmode=require&ssl_min_protocol_version=TLSv1.3' + ) + with self.assertRaisesRegex(ssl.SSLError, 'protocol version'): + await self.connect( + dsn='postgresql://ssl_user@localhost/postgres' + '?sslmode=require' + '&ssl_min_protocol_version=TLSv1.1' + '&ssl_max_protocol_version=TLSv1.1' + ) + with self.assertRaisesRegex(ssl.SSLError, 'no protocols'): + await self.connect( + dsn='postgresql://ssl_user@localhost/postgres' + '?sslmode=require' + '&ssl_min_protocol_version=TLSv1.2' + '&ssl_max_protocol_version=TLSv1.1' + ) + con = await self.connect( + dsn='postgresql://ssl_user@localhost/postgres?sslmode=require' + '&ssl_min_protocol_version=TLSv1.2' + '&ssl_max_protocol_version=TLSv1.2' + ) + try: + self.assertEqual(await con.fetchval('SELECT 42'), 42) + finally: + await con.close() + finally: + self.loop.set_exception_handler(old_handler) + @unittest.skipIf(os.environ.get('PGHOST'), 'unmanaged cluster') class TestClientSSLConnection(BaseTestSSLConnection): From 3badfce26e17c8ddde3594f2c5a187703b5f2f37 Mon Sep 17 00:00:00 2001 From: Fantix King Date: Tue, 14 Sep 2021 18:26:24 -0400 Subject: [PATCH 4/6] Fix for Python 3.6, win32, pg11 and lack of TLSv1.1 --- asyncpg/connect_utils.py | 28 +++++++++++++++------- tests/test_connect.py | 50 ++++++++++++++++++++++------------------ 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/asyncpg/connect_utils.py b/asyncpg/connect_utils.py index 7ebcc0cd..6f7dd44c 100644 --- a/asyncpg/connect_utils.py +++ b/asyncpg/connect_utils.py @@ -222,6 +222,10 @@ def _parse_hostlist(hostlist, port, *, unquote=False): def _parse_tls_version(tls_version): + if not hasattr(ssl_module, 'TLSVersion'): + raise ValueError( + "TLSVersion is not supported in this version of Python" + ) if tls_version.startswith('SSL'): raise ValueError( f"Unsupported TLS version: {tls_version}" @@ -234,6 +238,10 @@ def _parse_tls_version(tls_version): ) +def _dot_postgresql_path(filename) -> pathlib.Path: + return (pathlib.Path.home() / '.postgresql' / filename).resolve() + + def _parse_connect_dsn_and_args(*, dsn, host, port, user, password, passfile, database, ssl, connect_timeout, server_settings): @@ -485,7 +493,7 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, ssl.load_verify_locations(cafile=sslrootcert) ssl.verify_mode = ssl_module.CERT_REQUIRED else: - sslrootcert = os.path.expanduser('~/.postgresql/root.crt') + sslrootcert = _dot_postgresql_path('root.crt') try: ssl.load_verify_locations(cafile=sslrootcert) except FileNotFoundError: @@ -509,7 +517,7 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, ssl.load_verify_locations(cafile=sslcrl) ssl.verify_flags |= ssl_module.VERIFY_CRL_CHECK_CHAIN else: - sslcrl = os.path.expanduser('~/.postgresql/root.crl') + sslcrl = _dot_postgresql_path('root.crl') try: ssl.load_verify_locations(cafile=sslcrl) except FileNotFoundError: @@ -520,8 +528,8 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, if sslkey is None: sslkey = os.getenv('PGSSLKEY') if not sslkey: - sslkey = os.path.expanduser('~/.postgresql/postgresql.key') - if not os.path.exists(sslkey): + sslkey = _dot_postgresql_path('postgresql.key') + if not sslkey.exists(): sslkey = None if not sslpassword: sslpassword = '' @@ -532,7 +540,7 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, sslcert, keyfile=sslkey, password=lambda: sslpassword ) else: - sslcert = os.path.expanduser('~/.postgresql/postgresql.crt') + sslcert = _dot_postgresql_path('postgresql.crt') try: ssl.load_cert_chain( sslcert, keyfile=sslkey, password=lambda: sslpassword @@ -552,13 +560,17 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, ssl.options &= ~ssl_module.OP_NO_COMPRESSION if ssl_min_protocol_version is None: - ssl_min_protocol_version = os.getenv( - 'PGSSLMINPROTOCOLVERSION', 'TLSv1.2' - ) + ssl_min_protocol_version = os.getenv('PGSSLMINPROTOCOLVERSION') if ssl_min_protocol_version: ssl.minimum_version = _parse_tls_version( ssl_min_protocol_version ) + else: + try: + ssl.minimum_version = _parse_tls_version('TLSv1.2') + except ValueError: + # Python 3.6 does not have ssl.TLSVersion + pass if ssl_max_protocol_version is None: ssl_max_protocol_version = os.getenv('PGSSLMAXPROTOCOLVERSION') diff --git a/tests/test_connect.py b/tests/test_connect.py index d69fdcaf..ab19e19c 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -9,10 +9,12 @@ import contextlib import ipaddress import os +import pathlib import platform import shutil import ssl import stat +import sys import tempfile import textwrap import unittest @@ -46,31 +48,26 @@ @contextlib.contextmanager def mock_dot_postgresql(*, ca=True, crl=False, client=False, protected=False): with tempfile.TemporaryDirectory() as temp_dir: - pg_home = os.path.join(temp_dir, '.postgresql') - os.mkdir(pg_home) + home = pathlib.Path(temp_dir) + pg_home = home / '.postgresql' + pg_home.mkdir() if ca: - shutil.copyfile( - SSL_CA_CERT_FILE, os.path.join(pg_home, 'root.crt') - ) + shutil.copyfile(SSL_CA_CERT_FILE, pg_home / 'root.crt') if crl: - shutil.copyfile( - SSL_CA_CRL_FILE, os.path.join(pg_home, 'root.crl') - ) + shutil.copyfile(SSL_CA_CRL_FILE, pg_home / 'root.crl') if client: - shutil.copyfile( - CLIENT_SSL_CERT_FILE, os.path.join(pg_home, 'postgresql.crt') - ) + shutil.copyfile(CLIENT_SSL_CERT_FILE, pg_home / 'postgresql.crt') if protected: shutil.copyfile( - CLIENT_SSL_PROTECTED_KEY_FILE, - os.path.join(pg_home, 'postgresql.key'), + CLIENT_SSL_PROTECTED_KEY_FILE, pg_home / 'postgresql.key' ) else: shutil.copyfile( - CLIENT_SSL_KEY_FILE, - os.path.join(pg_home, 'postgresql.key'), + CLIENT_SSL_KEY_FILE, pg_home / 'postgresql.key' ) - with unittest.mock.patch.dict('os.environ', {'HOME': temp_dir}): + with unittest.mock.patch( + 'pathlib.Path.home', unittest.mock.Mock(return_value=home) + ): yield @@ -1213,9 +1210,10 @@ def get_server_settings(cls): 'ssl_cert_file': SSL_CERT_FILE, 'ssl_key_file': SSL_KEY_FILE, 'ssl_ca_file': CLIENT_CA_CERT_FILE, - 'ssl_min_protocol_version': 'TLSv1.2', - 'ssl_max_protocol_version': 'TLSv1.2', }) + if cls.cluster.get_pg_version() >= (12, 0): + conf['ssl_min_protocol_version'] = 'TLSv1.2' + conf['ssl_max_protocol_version'] = 'TLSv1.2' return conf @@ -1340,13 +1338,13 @@ async def verify_fails(sslmode, *, host='localhost', exn_type): await verify_works('allow') await verify_works('prefer') await verify_fails('require', - exn_type=ssl.CertificateError) + exn_type=ssl.SSLError) await verify_fails('verify-ca', - exn_type=ssl.CertificateError) + exn_type=ssl.SSLError) await verify_fails('verify-ca', host='127.0.0.1', - exn_type=ssl.CertificateError) + exn_type=ssl.SSLError) await verify_fails('verify-full', - exn_type=ssl.CertificateError) + exn_type=ssl.SSLError) async def test_ssl_connection_default_context(self): # XXX: uvloop artifact @@ -1410,7 +1408,13 @@ async def test_executemany_uvloop_ssl_issue_700(self): finally: await con.close() + @unittest.skipIf( + sys.version_info < (3, 7), "Python < 3.7 doesn't have ssl.TLSVersion" + ) async def test_tls_version(self): + if self.cluster.get_pg_version() < (12, 0): + self.skipTest("PostgreSQL < 12 cannot set ssl protocol version") + # XXX: uvloop artifact old_handler = self.loop.get_exception_handler() try: @@ -1420,7 +1424,7 @@ async def test_tls_version(self): dsn='postgresql://ssl_user@localhost/postgres' '?sslmode=require&ssl_min_protocol_version=TLSv1.3' ) - with self.assertRaisesRegex(ssl.SSLError, 'protocol version'): + with self.assertRaises(ssl.SSLError): await self.connect( dsn='postgresql://ssl_user@localhost/postgres' '?sslmode=require' From 9e7385209f3ca86c8c23934bd4ce8de2aefaa1eb Mon Sep 17 00:00:00 2001 From: Fantix King Date: Wed, 15 Sep 2021 10:41:27 -0400 Subject: [PATCH 5/6] CRF: remove sslcompression --- asyncpg/connect_utils.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/asyncpg/connect_utils.py b/asyncpg/connect_utils.py index 6f7dd44c..f6f9d651 100644 --- a/asyncpg/connect_utils.py +++ b/asyncpg/connect_utils.py @@ -249,7 +249,7 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, # of reading the pgpass file. auth_hosts = None sslcert = sslkey = sslrootcert = sslcrl = sslpassword = None - sslcompression = ssl_min_protocol_version = ssl_max_protocol_version = None + ssl_min_protocol_version = ssl_max_protocol_version = None if dsn: parsed = urllib.parse.urlparse(dsn) @@ -349,9 +349,6 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, if 'sslpassword' in query: sslpassword = query.pop('sslpassword') - if 'sslcompression' in query: - sslcompression = query.pop('sslcompression') - if 'ssl_min_protocol_version' in query: ssl_min_protocol_version = query.pop( 'ssl_min_protocol_version' @@ -554,11 +551,6 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, if keylogfile and not sys.flags.ignore_environment: ssl.keylog_filename = keylogfile - if sslcompression is None: - sslcompression = os.getenv('PGSSLCOMPRESSION') - if sslcompression == '1': - ssl.options &= ~ssl_module.OP_NO_COMPRESSION - if ssl_min_protocol_version is None: ssl_min_protocol_version = os.getenv('PGSSLMINPROTOCOLVERSION') if ssl_min_protocol_version: From f510d1f898e0475e45fc6984160222db58a01f31 Mon Sep 17 00:00:00 2001 From: Fantix King Date: Wed, 15 Sep 2021 14:14:18 -0400 Subject: [PATCH 6/6] Update asyncpg/connection.py Co-authored-by: Elvis Pranskevichus --- asyncpg/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncpg/connection.py b/asyncpg/connection.py index 1c7ca801..a7ec7719 100644 --- a/asyncpg/connection.py +++ b/asyncpg/connection.py @@ -2021,7 +2021,7 @@ async def connect(dsn=None, *, are supported in the *dsn* argument. .. versionchanged:: 0.25.0 - The ``sslpassword``, ``sslcompression``, ``ssl_min_protocol_version``, + The ``sslpassword``, ``ssl_min_protocol_version``, and ``ssl_max_protocol_version`` options are supported in the *dsn* argument. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy