Skip to content

Commit c674e86

Browse files
jdobeselprans
andauthored
Add support for sslcert, sslkey and sslrootcert parameters to DSN (MagicStack#768)
Co-authored-by: Elvis Pranskevichus <[email protected]>
1 parent 41da093 commit c674e86

10 files changed

+303
-10
lines changed

asyncpg/_testbase/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,10 @@ def tearDownClass(cls):
330330
@classmethod
331331
def get_connection_spec(cls, kwargs={}):
332332
conn_spec = cls.cluster.get_connection_spec()
333+
if kwargs.get('dsn'):
334+
conn_spec.pop('host')
333335
conn_spec.update(kwargs)
334-
if not os.environ.get('PGHOST'):
336+
if not os.environ.get('PGHOST') and not kwargs.get('dsn'):
335337
if 'database' not in conn_spec:
336338
conn_spec['database'] = 'postgres'
337339
if 'user' not in conn_spec:

asyncpg/connect_utils.py

+46-2
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
226226
# `auth_hosts` is the version of host information for the purposes
227227
# of reading the pgpass file.
228228
auth_hosts = None
229+
sslcert = sslkey = sslrootcert = sslcrl = None
229230

230231
if dsn:
231232
parsed = urllib.parse.urlparse(dsn)
@@ -310,6 +311,26 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
310311
if ssl is None:
311312
ssl = val
312313

314+
if 'sslcert' in query:
315+
val = query.pop('sslcert')
316+
if sslcert is None:
317+
sslcert = val
318+
319+
if 'sslkey' in query:
320+
val = query.pop('sslkey')
321+
if sslkey is None:
322+
sslkey = val
323+
324+
if 'sslrootcert' in query:
325+
val = query.pop('sslrootcert')
326+
if sslrootcert is None:
327+
sslrootcert = val
328+
329+
if 'sslcrl' in query:
330+
val = query.pop('sslcrl')
331+
if sslcrl is None:
332+
sslcrl = val
333+
313334
if query:
314335
if server_settings is None:
315336
server_settings = query
@@ -427,15 +448,38 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
427448
'`sslmode` parameter must be one of: {}'.format(modes))
428449

429450
# docs at https://www.postgresql.org/docs/10/static/libpq-connect.html
430-
# Not implemented: sslcert & sslkey & sslrootcert & sslcrl params.
431451
if sslmode < SSLMode.allow:
432452
ssl = False
433453
else:
434-
ssl = ssl_module.create_default_context()
454+
ssl = ssl_module.create_default_context(
455+
ssl_module.Purpose.SERVER_AUTH)
435456
ssl.check_hostname = sslmode >= SSLMode.verify_full
436457
ssl.verify_mode = ssl_module.CERT_REQUIRED
437458
if sslmode <= SSLMode.require:
438459
ssl.verify_mode = ssl_module.CERT_NONE
460+
461+
if sslcert is None:
462+
sslcert = os.getenv('PGSSLCERT')
463+
464+
if sslkey is None:
465+
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+
473+
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
482+
439483
elif ssl is True:
440484
ssl = ssl_module.create_default_context()
441485
sslmode = SSLMode.verify_full

asyncpg/connection.py

+54-4
Original file line numberDiff line numberDiff line change
@@ -1787,10 +1787,11 @@ async def connect(dsn=None, *,
17871787
Connection arguments specified using as a single string in the
17881788
`libpq connection URI format`_:
17891789
``postgres://user:password@host:port/database?option=value``.
1790-
The following options are recognized by asyncpg: host, port,
1791-
user, database (or dbname), password, passfile, sslmode.
1792-
Unlike libpq, asyncpg will treat unrecognized options
1793-
as `server settings`_ to be used for the connection.
1790+
The following options are recognized by asyncpg: ``host``,
1791+
``port``, ``user``, ``database`` (or ``dbname``), ``password``,
1792+
``passfile``, ``sslmode``, ``sslcert``, ``sslkey``, ``sslrootcert``,
1793+
and ``sslcrl``. Unlike libpq, asyncpg will treat unrecognized
1794+
options as `server settings`_ to be used for the connection.
17941795
17951796
.. note::
17961797
@@ -1912,6 +1913,51 @@ async def connect(dsn=None, *,
19121913
19131914
*ssl* is ignored for Unix domain socket communication.
19141915
1916+
Example of programmatic SSL context configuration that is equivalent
1917+
to ``sslmode=verify-full&sslcert=..&sslkey=..&sslrootcert=..``:
1918+
1919+
.. code-block:: pycon
1920+
1921+
>>> import asyncpg
1922+
>>> import asyncio
1923+
>>> import ssl
1924+
>>> async def main():
1925+
... # Load CA bundle for server certificate verification,
1926+
... # equivalent to sslrootcert= in DSN.
1927+
... sslctx = ssl.create_default_context(
1928+
... ssl.Purpose.SERVER_AUTH,
1929+
... cafile="path/to/ca_bundle.pem")
1930+
... # If True, equivalent to sslmode=verify-full, if False:
1931+
... # sslmode=verify-ca.
1932+
... sslctx.check_hostname = True
1933+
... # Load client certificate and private key for client
1934+
... # authentication, equivalent to sslcert= and sslkey= in
1935+
... # DSN.
1936+
... sslctx.load_cert_chain(
1937+
... "path/to/client.cert",
1938+
... keyfile="path/to/client.key",
1939+
... )
1940+
... con = await asyncpg.connect(user='postgres', ssl=sslctx)
1941+
... await con.close()
1942+
>>> asyncio.run(run())
1943+
1944+
Example of programmatic SSL context configuration that is equivalent
1945+
to ``sslmode=require`` (no server certificate or host verification):
1946+
1947+
.. code-block:: pycon
1948+
1949+
>>> import asyncpg
1950+
>>> import asyncio
1951+
>>> import ssl
1952+
>>> async def main():
1953+
... sslctx = ssl.create_default_context(
1954+
... ssl.Purpose.SERVER_AUTH)
1955+
... sslctx.check_hostname = False
1956+
... sslctx.verify_mode = ssl.CERT_NONE
1957+
... con = await asyncpg.connect(user='postgres', ssl=sslctx)
1958+
... await con.close()
1959+
>>> asyncio.run(run())
1960+
19151961
:param dict server_settings:
19161962
An optional dict of server runtime parameters. Refer to
19171963
PostgreSQL documentation for
@@ -1970,6 +2016,10 @@ async def connect(dsn=None, *,
19702016
.. versionchanged:: 0.22.0
19712017
The *ssl* argument now defaults to ``'prefer'``.
19722018
2019+
.. versionchanged:: 0.24.0
2020+
The ``sslcert``, ``sslkey``, ``sslrootcert``, and ``sslcrl`` options
2021+
are supported in the *dsn* argument.
2022+
19732023
.. _SSLContext: https://docs.python.org/3/library/ssl.html#ssl.SSLContext
19742024
.. _create_default_context:
19752025
https://docs.python.org/3/library/ssl.html#ssl.create_default_context

tests/certs/client.cert.pem

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIEAzCCAuugAwIBAgIUPfej8IQ/5bCrihqWImrq2vKPOq0wDQYJKoZIhvcNAQEL
3+
BQAwgaMxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdU
4+
b3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5j
5+
cGcgdGVzdHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0ExHTAbBgkq
6+
hkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvMB4XDTIxMDgwOTIxNTA1MloXDTMyMDEw
7+
NDIxNTA1MlowgZUxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYD
8+
VQQHDAdUb3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsM
9+
DWFzeW5jcGcgdGVzdHMxETAPBgNVBAMMCHNzbF91c2VyMR0wGwYJKoZIhvcNAQkB
10+
Fg5oZWxsb0BtYWdpYy5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
11+
AJjiP9Ik/KRRLK9GMvoH8m1LO+Gyrr8Gz36LpmKJMR/PpwTL+1pOkYSGhOyT3Cw9
12+
/kWWLJRCvYqKgFtYtbr4S6ReGm3GdSVW+sfVRYDrRQZLPgQSPeq25g2v8UZ63Ota
13+
lPAyUPUZKpxyWz8PL77lV8psb9yv14yBH2kv9BbxKPksWOU8p8OCn1Z3WFFl0ItO
14+
nzMvCp5os+xFrt4SpoRGTx9x4QleY+zrEsYZtmnV4wC+JuJkNw4fuCdrX5k7dghs
15+
uZkcsAZof1nMdYsYiazeDfQKZtJqh5kO7mpwvCudKUWaLJJUwiQA87BwSlnCd/Hh
16+
TZDbC+zeFNjTS49/4Q72xVECAwEAAaM7MDkwHwYDVR0jBBgwFoAUi1jMmAisuOib
17+
mHIE2n0W2WnnaL0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwDQYJKoZIhvcNAQEL
18+
BQADggEBACbnp5oOp639ko4jn8axF+so91k0vIcgwDg+NqgtSRsuAENGumHAa8ec
19+
YOks0TCTvNN5E6AfNSxRat5CyguIlJ/Vy3KbkkFNXcCIcI/duAJvNphg7JeqYlQM
20+
VIJhrO/5oNQMzzTw8XzTHnciGbrbiZ04hjwrruEkvmIAwgQPhIgq4H6umTZauTvk
21+
DEo7uLm7RuG9hnDyWCdJxLLljefNL/EAuDYpPzgTeEN6JAnOu0ULIbpxpJKiYEId
22+
8I0U2n0I2NTDOHmsAJiXf8BiHHmpK5SXFyY9s2ZuGkCzvmeZlR81tTXmHZ3v1X2z
23+
8NajoAZfJ+QD50DrbF5E00yovZbyIB4=
24+
-----END CERTIFICATE-----

tests/certs/client.csr.pem

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-----BEGIN CERTIFICATE REQUEST-----
2+
MIIC2zCCAcMCAQAwgZUxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAw
3+
DgYDVQQHDAdUb3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNV
4+
BAsMDWFzeW5jcGcgdGVzdHMxETAPBgNVBAMMCHNzbF91c2VyMR0wGwYJKoZIhvcN
5+
AQkBFg5oZWxsb0BtYWdpYy5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
6+
ggEBAJjiP9Ik/KRRLK9GMvoH8m1LO+Gyrr8Gz36LpmKJMR/PpwTL+1pOkYSGhOyT
7+
3Cw9/kWWLJRCvYqKgFtYtbr4S6ReGm3GdSVW+sfVRYDrRQZLPgQSPeq25g2v8UZ6
8+
3OtalPAyUPUZKpxyWz8PL77lV8psb9yv14yBH2kv9BbxKPksWOU8p8OCn1Z3WFFl
9+
0ItOnzMvCp5os+xFrt4SpoRGTx9x4QleY+zrEsYZtmnV4wC+JuJkNw4fuCdrX5k7
10+
dghsuZkcsAZof1nMdYsYiazeDfQKZtJqh5kO7mpwvCudKUWaLJJUwiQA87BwSlnC
11+
d/HhTZDbC+zeFNjTS49/4Q72xVECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCG
12+
irI2ph09V/4BMe6QMhjBFUatwmTa/05PYGjvT3LAhRzEb3/o/gca0XFSAFrE6zIY
13+
DsgMk1c8aLr9DQsn9cf22oMFImKdnIZ3WLE9MXjN+s1Bjkiqt7uxDpxPo/DdfUTQ
14+
RQC5i/Z2tn29y9K09lEjp35ZhPp3tOA0V4CH0FThAjRR+amwaBjxQ7TTSNfoMUd7
15+
i/DrylwnNg1iEQmYUwJYopqgxtwseiBUSDXzEvjFPY4AvZKmEQmE5QkybpWIfivt
16+
1kmKhvKKpn5Cb6c0D3XoYqyPN3TxqjH9L8R+tWUCwhYJeDZj5DumFr3Hw/sx8tOL
17+
EctyS6XfO3S2KbmDiyv8
18+
-----END CERTIFICATE REQUEST-----

tests/certs/client.key.pem

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEowIBAAKCAQEAmOI/0iT8pFEsr0Yy+gfybUs74bKuvwbPfoumYokxH8+nBMv7
3+
Wk6RhIaE7JPcLD3+RZYslEK9ioqAW1i1uvhLpF4abcZ1JVb6x9VFgOtFBks+BBI9
4+
6rbmDa/xRnrc61qU8DJQ9RkqnHJbPw8vvuVXymxv3K/XjIEfaS/0FvEo+SxY5Tyn
5+
w4KfVndYUWXQi06fMy8Knmiz7EWu3hKmhEZPH3HhCV5j7OsSxhm2adXjAL4m4mQ3
6+
Dh+4J2tfmTt2CGy5mRywBmh/Wcx1ixiJrN4N9Apm0mqHmQ7uanC8K50pRZosklTC
7+
JADzsHBKWcJ38eFNkNsL7N4U2NNLj3/hDvbFUQIDAQABAoIBAAIMVeqM0E2rQLwA
8+
ZsJuxNKuBVlauXiZsMHzQQFk8SGJ+KTZzr5A+zYZT0KUIIj/M57fCi3aTwvCG0Ie
9+
CCE/HlRPZm8+D2e2qJlwxAOcI0qYS3ZmgCna1W4tgz/8eWU1y3UEV41RDv8VkR9h
10+
JrSaAfkWRtFgEbUyLaeNGuoLxQ7Bggo9zi1/xDJz/aZ/y4L4y8l1xs2eNVmbRGnj
11+
mPr1daeYhsWgaNiT/Wm3CAxvykptHavyWSsrXzCp0bEw6fAXxBqkeDFGIMVC9q3t
12+
ZRFtqMHi9i7SJtH1XauOC6QxLYgSEmNEie1JYbNx2Zf4h2KvSwDxpTqWhOjJ/m5j
13+
/NSkASECgYEAyHQAqG90yz5QaYnC9lgUhGIMokg9O3LcEbeK7IKIPtC9xINOrnj6
14+
ecCfhfc1aP3wQI+VKC3kiYerfTJvVsU5CEawBQSRiBY/TZZ7hTR7Rkm3s4xeM+o6
15+
2zADdVUwmTVYwu0gUKCeDKO4iD8Uhh8J54JrKUejuG50VWZQWGVgqo0CgYEAwz+2
16+
VdYcfuQykMA3jQBnXmMMK92/Toq6FPDgsa45guEFD6Zfdi9347/0Ipt+cTNg0sUZ
17+
YBLOnNPwLn+yInfFa88Myf0UxCAOoZKfpJg/J27soUJzpd/CGx+vaAHrxMP6t/qo
18+
JAGMBIyOoqquId7jvErlC/sGBk/duya7IdiT1tUCgYBuvM8EPhaKlVE9DJL9Hmmv
19+
PK94E2poZiq3SutffzkfYpgDcPrNnh3ZlxVJn+kMqITKVcfz226On7mYP32MtQWt
20+
0cc57m0rfgbYqRJx4y1bBiyK7ze3fGWpYxv1/OsNKJBxlygsAp9toiC2fAqtkYYa
21+
NE1ZD6+dmr9/0jb+rnq5nQKBgQCtZvwsp4ePOmOeItgzJdSoAxdgLgQlYRd6WaN0
22+
qeLx1Z6FE6FceTPk1SmhQq+9IYAwMFQk+w78QU3iPg6ahfyTjsMw8M9sj3vvCyU1
23+
LPGJt/34CehjvKHLLQy/NlWJ3vPgSYDi2Wzc7WgQF72m3ykqpOlfBoWHPY8TE4bG
24+
vG4wMQKBgFSq2GDAJ1ovBl7yWYW7w4SM8X96YPOff+OmI4G/8+U7u3dDM1dYeQxD
25+
7BHLuvr4AXg27LC97u8/eFIBXC1elbco/nAKE1YHj2xcIb/4TsgAqkcysGV08ngi
26+
dULh3q0GpTYyuELZV4bfWE8MjSiGAH+nuMdXYDGuY2QnBq8MdSOH
27+
-----END RSA PRIVATE KEY-----

tests/certs/client_ca.cert.pem

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIEKTCCAxGgAwIBAgIUKmL8tfNS9LIB6GLB9RpZpTyk3uIwDQYJKoZIhvcNAQEL
3+
BQAwgaMxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdU
4+
b3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5j
5+
cGcgdGVzdHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0ExHTAbBgkq
6+
hkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvMB4XDTIxMDgwOTIxNDQxM1oXDTQxMDgw
7+
NDIxNDQxM1owgaMxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYD
8+
VQQHDAdUb3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsM
9+
DWFzeW5jcGcgdGVzdHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0Ex
10+
HTAbBgkqhkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvMIIBIjANBgkqhkiG9w0BAQEF
11+
AAOCAQ8AMIIBCgKCAQEAptRYfxKiWExfZguQDva53bIqYa4lJwZA86Qu0peBUcsd
12+
E6zyHNgVv4XSMim1FH12KQ4KPKuQAcVqRMCRAHqB96kUfWQqF//fLajr0umdzcbx
13+
+UTgNux8TkScTl9KNAxhiR/oOGbKFcNSs4raaG8puwwEN66uMhoKk2pN2NwDVfHa
14+
bTekJ3jouTcTCnqCynx4qwI4WStJkuW4IPCmDRVXxOOauT7YalElYLWYtAOqGEvf
15+
noDK2Imhc0h6B5XW8nI54rVCXWwhW1v3RLAJGP+LwSy++bf08xmpHXdKkAj5BmUO
16+
QwJRiJ33Xa17rmi385egx8KpqV04YEAPdV1Z4QM6PQIDAQABo1MwUTAdBgNVHQ4E
17+
FgQUi1jMmAisuOibmHIE2n0W2WnnaL0wHwYDVR0jBBgwFoAUi1jMmAisuOibmHIE
18+
2n0W2WnnaL0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAifNE
19+
ZLZXxECp2Sl6jCViZxgFf2+OHDvRORgI6J0heckYyYF/JHvLaDphh6TkSJAdT6Y3
20+
hAb7jueTMI+6RIdRzIjTKCGdJqUetiSfAbnQyIp2qmVqdjeFoXTvQL7BdkIE+kOW
21+
0iomMqDB3czTl//LrgVQCYqKM0D/Ytecpg2mbshLfpPxdHyliCJcb4SqfdrDnKoV
22+
HUduBjOVot+6bkB5SEGCrrB4KMFTzbAu+zriKWWz+uycIyeVMLEyhDs59vptOK6e
23+
gWkraG43LZY3cHPiVeN3tA/dWdyJf9rgK21zQDSMB8OSH4yQjdQmkkvRQBjp3Fcy
24+
w2SZIP4o9l1Y7+hMMw==
25+
-----END CERTIFICATE-----

tests/certs/client_ca.cert.srl

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3DF7A3F0843FE5B0AB8A1A96226AEADAF28F3AAD

tests/certs/client_ca.key.pem

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEpAIBAAKCAQEAptRYfxKiWExfZguQDva53bIqYa4lJwZA86Qu0peBUcsdE6zy
3+
HNgVv4XSMim1FH12KQ4KPKuQAcVqRMCRAHqB96kUfWQqF//fLajr0umdzcbx+UTg
4+
Nux8TkScTl9KNAxhiR/oOGbKFcNSs4raaG8puwwEN66uMhoKk2pN2NwDVfHabTek
5+
J3jouTcTCnqCynx4qwI4WStJkuW4IPCmDRVXxOOauT7YalElYLWYtAOqGEvfnoDK
6+
2Imhc0h6B5XW8nI54rVCXWwhW1v3RLAJGP+LwSy++bf08xmpHXdKkAj5BmUOQwJR
7+
iJ33Xa17rmi385egx8KpqV04YEAPdV1Z4QM6PQIDAQABAoIBABQrKcO7CftoyEO6
8+
9CCK/W9q4arLddxg6itKVwrInC66QnqlduO7z+1GjWHZHvYqMMXH17778r30EuPa
9+
7+zB4sKBI2QBXwFlwqJvgIsQCS7edVRwWjbpoiGIM+lZpcvjD0uXmuhurNGyumXQ
10+
TJVBkyb0zfG5YX/XHB40RNMJzjFuiMPDLVQmmDE//FOuWqBG88MgJP9Ghk3J7wA2
11+
JfDPavb49EzOCSh74zJWP7/QyybzF3ABCMu4OFkaOdqso8FS659XI55QReBbUppu
12+
FRkOgao1BclJhbBdrdtLNjlETM82tfVgW56vaIrrU2z7HskihEyMdB4c+CYbBnPx
13+
QqIhkhUCgYEA0SLVExtNy5Gmi6/ZY9tcd3QIuxcN6Xiup+LgIhWK3+GIoVOPsOjN
14+
27dlVRINPKhrCfVbrLxUtDN5PzphwSA2Qddm4jg3d5FzX+FgKHQpoaU1WjtRPP+w
15+
K+t6W/NbZ8Rn4JyhZQ3Yqj264NA2l3QmuTfZSUQ5m4x7EUakfGU7G1sCgYEAzDaU
16+
jHsovn0FedOUaaYl6pgzjFV8ByPeT9usN54PZyuzyc+WunjJkxCQqD88J9jyG8XB
17+
3V3tQj/CNbMczrS2ZaJ29aI4b/8NwBNR9e6t01bY3B90GJi8S4B4Hf8tYyIlVdeL
18+
tCC4FCZhvl4peaK3AWBj4NhjvdB32ThDXSGxLEcCgYEAiA5tKHz+44ziGMZSW1B+
19+
m4f1liGtf1Jv7fD/d60kJ/qF9M50ENej9Wkel3Wi/u9ik5v4BCyRvpouKyBEMGxQ
20+
YA1OdaW1ECikMqBg+nB4FR1x1D364ABIEIqlk+SCdsOkANBlf2S+rCJ0zYUnvuhl
21+
uOHIjo3AHJ4MAnU+1V7WUTkCgYBkMedioc7U34x/QJNR3sY9ux2Xnh2zdyLNdc+i
22+
njeafDPDMcoXhcoJERiYpCYEuwnXHIlI7pvJZHUKWe4pcTsI1NSfIk+ki7SYaCJP
23+
kyLQTY0rO3d/1fiU5tyIgzomqIs++fm+kEsg/8/3UkXxOyelUkDPAfy2FgGnn1ZV
24+
7ID8YwKBgQCeZCapdGJ6Iu5oYB17TyE5pLwb+QzaofR5uO8H4pXGVQyilKVCG9Dp
25+
GMnlXD7bwXPVKa8Icow2OIbmgrZ2mzOo9BSY3BlkKbpJDy7UNtAhzsHHN5/AEk8z
26+
YycWQtMiXI+cRsYO0eyHhJeSS2hX+JTe++iZX65twV53agzCHWRIbg==
27+
-----END RSA PRIVATE KEY-----

0 commit comments

Comments
 (0)