From dae06d1921b3e96b8a2d44b3a67dfef90f37b6d9 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Thu, 7 Sep 2023 22:31:17 +0200 Subject: [PATCH 01/10] Expose retrieving certificate chains in SSL module --- Doc/library/ssl.rst | 23 +++++++++++++++++++---- Lib/ssl.py | 24 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 19225c85ff7624..6e5d9a19c03e2c 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -42,8 +42,10 @@ This module provides a class, :class:`ssl.SSLSocket`, which is derived from the :class:`socket.socket` type, and provides a socket-like wrapper that also encrypts and decrypts the data going over the socket with SSL. It supports additional methods such as :meth:`getpeercert`, which retrieves the -certificate of the other side of the connection, and :meth:`cipher`, which -retrieves the cipher being used for the secure connection. +certificate of the other side of the connection, :meth:`cipher`, which +retrieves the cipher being used for the secure connection or +:meth:`get_verified_chain`, :meth:`get_unverified_chain` which retrieves +certificate chain. For more sophisticated applications, the :class:`ssl.SSLContext` class helps manage settings and certificates, which can then be inherited @@ -1221,6 +1223,16 @@ SSL sockets also have the following additional methods and attributes: .. versionchanged:: 3.9 IPv6 address strings no longer have a trailing new line. +.. method:: SSLSocket.get_verified_chain() + + Returns verified verified certificate chain provided by the other + end of the SSL channel. Return ``None`` if no certificates were provided. + +.. method:: SSLSocket.get_unverified_chain() + + Returns unverified verified certificate chain provided by the other + end of the SSL channel. Return ``None`` if no certificates were provided. + .. method:: SSLSocket.cipher() Returns a three-value tuple containing the name of the cipher being used, the @@ -1670,8 +1682,9 @@ to speed up repeated connections from the same clients. Due to the early negotiation phase of the TLS connection, only limited methods and attributes are usable like :meth:`SSLSocket.selected_alpn_protocol` and :attr:`SSLSocket.context`. - The :meth:`SSLSocket.getpeercert`, - :meth:`SSLSocket.cipher` and :meth:`SSLSocket.compression` methods require that + The :meth:`SSLSocket.getpeercert`, :meth:`SSLSocket.get_verified_chain`, + :meth:`SSLSocket.get_unverified_chain` :meth:`SSLSocket.cipher` + and :meth:`SSLSocket.compression` methods require that the TLS connection has progressed beyond the TLS Client Hello and therefore will not return meaningful values nor can they be called safely. @@ -2428,6 +2441,8 @@ provided. - :meth:`~SSLSocket.read` - :meth:`~SSLSocket.write` - :meth:`~SSLSocket.getpeercert` + - :meth:`~SSLSocket.get_verified_chain` + - :meth:`~SSLSocket.get_unverified_chain` - :meth:`~SSLSocket.selected_alpn_protocol` - :meth:`~SSLSocket.selected_npn_protocol` - :meth:`~SSLSocket.cipher` diff --git a/Lib/ssl.py b/Lib/ssl.py index 1d5873726441e4..e5f4da72ab1b6b 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -876,6 +876,22 @@ def getpeercert(self, binary_form=False): """ return self._sslobj.getpeercert(binary_form) + def get_verified_chain(self): + """Returns verified verified certificate chain provided by the other + end of the SSL channel. + + Return None if no certificates were provided. + """ + return self._sslobj.get_verified_chain() + + def get_unverified_chain(self): + """Returns unverified verified certificate chain provided by the other + end of the SSL channel. + + Return None if no certificates were provided. + """ + return self._sslobj.get_unverified_chain() + def selected_npn_protocol(self): """Return the currently selected NPN protocol as a string, or ``None`` if a next protocol was not negotiated or if NPN is not supported by one @@ -1096,6 +1112,14 @@ def getpeercert(self, binary_form=False): self._check_connected() return self._sslobj.getpeercert(binary_form) + @_sslcopydoc + def get_verified_chain(self): + return self._sslobj.get_verified_chain() + + @_sslcopydoc + def get_unverified_chain(self): + return self._sslobj.get_unverified_chain() + @_sslcopydoc def selected_npn_protocol(self): self._checkClosed() From 1418dd109022cdff4432e332cffba4f19b143cb4 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Fri, 8 Sep 2023 09:53:13 +0200 Subject: [PATCH 02/10] Review remarks applied --- Doc/library/ssl.rst | 14 ++++++++++---- Lib/ssl.py | 14 ++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 58c8f58463b2a8..7288b85c5a8038 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1214,13 +1214,19 @@ SSL sockets also have the following additional methods and attributes: .. method:: SSLSocket.get_verified_chain() - Returns verified verified certificate chain provided by the other - end of the SSL channel. Return ``None`` if no certificates were provided. + Returns verified certificate chain provided by the other + end of the SSL channel as a list of ``_ssl.Certificate``. + Return ``None`` if no certificates were provided. + + .. versionadded:: 3.13 .. method:: SSLSocket.get_unverified_chain() - Returns unverified verified certificate chain provided by the other - end of the SSL channel. Return ``None`` if no certificates were provided. + Returns unverified certificate chain provided by the other + end of the SSL channel as a list of ``_ssl.Certificate``. + Return ``None`` if no certificates were provided. + + .. versionadded:: 3.13 .. method:: SSLSocket.cipher() diff --git a/Lib/ssl.py b/Lib/ssl.py index 1b7f284357f755..d768487d88dccd 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -877,18 +877,16 @@ def getpeercert(self, binary_form=False): return self._sslobj.getpeercert(binary_form) def get_verified_chain(self): - """Returns verified verified certificate chain provided by the other - end of the SSL channel. - - Return None if no certificates were provided. + """Returns verified certificate chain provided by the other + end of the SSL channel as a list of ``_ssl.Certificate``. + Return ``None`` if no certificates were provided. """ return self._sslobj.get_verified_chain() def get_unverified_chain(self): - """Returns unverified verified certificate chain provided by the other - end of the SSL channel. - - Return None if no certificates were provided. + """Returns unverified certificate chain provided by the other + end of the SSL channel as a list of ``_ssl.Certificate``. + Return ``None`` if no certificates were provided. """ return self._sslobj.get_unverified_chain() From 0cdfe19425ba0de60ad23431792ad835267a0213 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Fri, 8 Sep 2023 12:21:04 +0200 Subject: [PATCH 03/10] Trim white spaces --- Doc/library/ssl.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 7288b85c5a8038..4e24ace624bb89 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1218,7 +1218,7 @@ SSL sockets also have the following additional methods and attributes: end of the SSL channel as a list of ``_ssl.Certificate``. Return ``None`` if no certificates were provided. - .. versionadded:: 3.13 + .. versionadded:: 3.13 .. method:: SSLSocket.get_unverified_chain() @@ -1226,7 +1226,7 @@ SSL sockets also have the following additional methods and attributes: end of the SSL channel as a list of ``_ssl.Certificate``. Return ``None`` if no certificates were provided. - .. versionadded:: 3.13 + .. versionadded:: 3.13 .. method:: SSLSocket.cipher() From 4a51db29416e7a5f09fa5394280bc7b20c1c8c56 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Mon, 11 Sep 2023 13:28:47 +0200 Subject: [PATCH 04/10] Review fixes. --- Doc/library/ssl.rst | 6 ++---- Lib/ssl.py | 20 ++++++++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 4e24ace624bb89..75c308e57c5c46 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1215,16 +1215,14 @@ SSL sockets also have the following additional methods and attributes: .. method:: SSLSocket.get_verified_chain() Returns verified certificate chain provided by the other - end of the SSL channel as a list of ``_ssl.Certificate``. - Return ``None`` if no certificates were provided. + end of the SSL channel as a list of DER-encoded bytes. .. versionadded:: 3.13 .. method:: SSLSocket.get_unverified_chain() Returns unverified certificate chain provided by the other - end of the SSL channel as a list of ``_ssl.Certificate``. - Return ``None`` if no certificates were provided. + end of the SSL channel as a list of DER-encoded bytes. .. versionadded:: 3.13 diff --git a/Lib/ssl.py b/Lib/ssl.py index d768487d88dccd..ffcf5255e34806 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -878,17 +878,25 @@ def getpeercert(self, binary_form=False): def get_verified_chain(self): """Returns verified certificate chain provided by the other - end of the SSL channel as a list of ``_ssl.Certificate``. - Return ``None`` if no certificates were provided. + end of the SSL channel as a list of DER-encoded bytes. """ - return self._sslobj.get_verified_chain() + chain = self._sslobj.get_verified_chain() + + if chain is None: + return [] + + return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain] def get_unverified_chain(self): """Returns unverified certificate chain provided by the other - end of the SSL channel as a list of ``_ssl.Certificate``. - Return ``None`` if no certificates were provided. + end of the SSL channel as a list of DER-encoded bytes. """ - return self._sslobj.get_unverified_chain() + chain = self._sslobj.get_verified_chain() + + if chain is None: + return [] + + return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain] def selected_npn_protocol(self): """Return the currently selected NPN protocol as a string, or ``None`` From 701c62a80af6fa8340a7ee0fedb10b146eaa360d Mon Sep 17 00:00:00 2001 From: Mateusz Nowak Date: Wed, 13 Sep 2023 15:42:01 +0200 Subject: [PATCH 05/10] Update Lib/ssl.py --- Lib/ssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ssl.py b/Lib/ssl.py index ffcf5255e34806..f5c2491afd5393 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -891,7 +891,7 @@ def get_unverified_chain(self): """Returns unverified certificate chain provided by the other end of the SSL channel as a list of DER-encoded bytes. """ - chain = self._sslobj.get_verified_chain() + chain = self._sslobj.get_unverified_chain() if chain is None: return [] From f925937080d235c8e6dc6b50f0d2d27d23b9dfc1 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Tue, 19 Sep 2023 22:33:40 +0200 Subject: [PATCH 06/10] Review fixes --- Doc/library/ssl.rst | 4 +++- Lib/ssl.py | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 75c308e57c5c46..92cf3de2a7b4cf 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1216,12 +1216,14 @@ SSL sockets also have the following additional methods and attributes: Returns verified certificate chain provided by the other end of the SSL channel as a list of DER-encoded bytes. + If certificate verification was disabled method acts the same as + :meth:`~SSLSocket.get_unverified_chain`. .. versionadded:: 3.13 .. method:: SSLSocket.get_unverified_chain() - Returns unverified certificate chain provided by the other + Returns raw certificate chain provided by the other end of the SSL channel as a list of DER-encoded bytes. .. versionadded:: 3.13 diff --git a/Lib/ssl.py b/Lib/ssl.py index f5c2491afd5393..62e55857141dfc 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -879,6 +879,9 @@ def getpeercert(self, binary_form=False): def get_verified_chain(self): """Returns verified certificate chain provided by the other end of the SSL channel as a list of DER-encoded bytes. + + If certificate verification was disabled method acts the same as + ``SSLSocket.get_unverified_chain``. """ chain = self._sslobj.get_verified_chain() @@ -888,7 +891,7 @@ def get_verified_chain(self): return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain] def get_unverified_chain(self): - """Returns unverified certificate chain provided by the other + """Returns raw certificate chain provided by the other end of the SSL channel as a list of DER-encoded bytes. """ chain = self._sslobj.get_unverified_chain() From 5cb139d7c7a80ab3e4a73c429b1d3b21343138a6 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Tue, 19 Sep 2023 17:56:28 -0700 Subject: [PATCH 07/10] NEWS entry. --- .../Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst diff --git a/Misc/NEWS.d/next/Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst b/Misc/NEWS.d/next/Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst new file mode 100644 index 00000000000000..e741e60ff41a9b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst @@ -0,0 +1,5 @@ +You can now get the raw TLS certificate chains from TLS connections via +:meth:`ssl.SSLSocket.get_verified_chain` and +:meth:`ssl.SSLSocket.get_unverified_chain` methods. + +Contributed by Mateusz Nowak. From d276aeb49a10471958508c1226061e71e2991b46 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Mon, 6 May 2024 22:01:19 +0200 Subject: [PATCH 08/10] Return consistent types for `get_un/verified_chain` in `SSLObject` and `SSLSocket` --- Lib/ssl.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Lib/ssl.py b/Lib/ssl.py index cc685c2cc405ab..f248e1404baf44 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -1165,11 +1165,21 @@ def getpeercert(self, binary_form=False): @_sslcopydoc def get_verified_chain(self): - return self._sslobj.get_verified_chain() + chain = self._sslobj.get_verified_chain() + + if chain is None: + return [] + + return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain] @_sslcopydoc def get_unverified_chain(self): - return self._sslobj.get_unverified_chain() + chain = self._sslobj.get_unverified_chain() + + if chain is None: + return [] + + return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain] @_sslcopydoc def selected_npn_protocol(self): From 19bf5f1447811afc6d2a19a7b93a1a0f1225c284 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Wed, 7 Aug 2024 21:43:18 +0200 Subject: [PATCH 09/10] Simple test for un/verified chain --- Lib/test/test_ssl.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 6ec010d13f9e7e..2c383096ed0227 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4720,6 +4720,28 @@ def test_internal_chain_client(self): ssl.PEM_cert_to_DER_cert(pem), der ) + def test_certificate_chain(self): + client_context, server_context, hostname = testing_context( + server_chain=False + ) + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket( + socket.socket(), + server_hostname=hostname + ) as s: + s.connect((HOST, server.port)) + vc = s.get_verified_chain() + self.assertEqual(len(vc), 2) + + ee, ca = vc + + uvc = s.get_unverified_chain() + self.assertEqual(len(uvc), 1) + + self.assertEqual(ee, uvc[0]) + self.assertNotEqual(ee, ca) + def test_internal_chain_server(self): client_context, server_context, hostname = testing_context() client_context.load_cert_chain(SIGNED_CERTFILE) From e0a1dc6b6176f4d6b1ad2c739946d356ac66879a Mon Sep 17 00:00:00 2001 From: matiuszka Date: Wed, 14 Aug 2024 14:18:43 +0200 Subject: [PATCH 10/10] Tests improvements --- Lib/test/certdata/cert3.pem | 34 ++++++++++++++++++++++++++++++++++ Lib/test/test_ssl.py | 13 +++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 Lib/test/certdata/cert3.pem diff --git a/Lib/test/certdata/cert3.pem b/Lib/test/certdata/cert3.pem new file mode 100644 index 00000000000000..034bc43ff1974e --- /dev/null +++ b/Lib/test/certdata/cert3.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF8TCCBFmgAwIBAgIJAMstgJlaaVJcMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx +NDIzMTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj +MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxv +Y2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKAqKHEL7aDt +3swl8hQF8VaK4zDGDRaF3E/IZTMwCN7FsQ4ejSiOe3E90f0phHCIpEpv2OebNenY +IpOGoFgkh62r/cthmnhu8Mn+FUIv17iOq7WX7B30OSqEpnr1voLX93XYkAq8LlMh +P79vsSCVhTwow3HZY7krEgl5WlfryOfj1i1TODSFPRCJePh66BsOTUvV/33GC+Qd +pVZVDGLowU1Ycmr/FdRvwT+F39Dehp03UFcxaX0/joPhH5gYpBB1kWTAQmxuqKMW +9ZZs6hrPtMXF/yfSrrXrzTdpct9paKR8RcufOcS8qju/ISK+1P/LXg2b5KJHedLo +TTIO3yCZ4d1odyuZBP7JDrI05gMJx95gz6sG685Qc+52MzLSTwr/Qg+MOjQoBy0o +8fRRVvIMEwoN0ZDb4uFEUuwZceUP1vTk/GGpNQt7ct4ropn6K4Zta3BUtovlLjZa +IIBhc1KETUqjRDvC6ACKmlcJ/5pY/dbH1lOux+IMFsh+djmaV90b3QIDAQABo4IB +wDCCAbwwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA4GA1UdDwEB/wQEAwIFoDAdBgNV +HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4E +FgQUP7HpT6C+MGY+ChjID0caTzRqD0IwfQYDVR0jBHYwdIAU8+yUjvKOMMSOaMK/ +jmoZwMGfdmWhUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcoIJAMst +gJlaaVJbMIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6Ly90ZXN0 +Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1BggrBgEFBQcw +AYYpaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2NzcC8wQwYD +VR0fBDwwOjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0 +Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAMo0usXQzycxMtYN +JzC42xfftzmnu7E7hsQx/fur22MazJCruU6rNEkMXow+cKOnay+nmiV7AVoYlkh2 ++DZ4dPq8fWh/5cqmnXvccr2jJVEXaOjp1wKGLH0WfLXcRLIK4/fJM6NRNoO81HDN +hJGfBrot0gUKZcPZVQmouAlpu5OGwrfCkHR8v/BdvA5jE4zr+g/x+uUScE0M64wu +okJCAAQP/PkfQZxjePBmk7KPLuiTHFDLLX+2uldvUmLXOQsJgqumU03MBT4Z8NTA +zqmtEM65ceSP8lo8Zbrcy+AEkCulFaZ92tyjtbe8oN4wTmTLFw06oFLSZzuiOgDV +OaphdVKf/pvA6KBpr6izox0KQFIE5z3AAJZfKzMGDDD20xhy7jjQZNMAhjfsT+k4 +SeYB/6KafNxq08uoulj7w4Z4R/EGpkXnU96ZHYHmvGN0RnxwI1cpYHCazG8AjsK/ +anN9brBi5twTGrn+D8LRBqF5Yn+2MKkD0EdXJdtIENHP+32sPQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 2c383096ed0227..9c415bd7d1c4e4 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -103,6 +103,7 @@ def data_file(*name): # Two keys and certs signed by the same CA (for SNI tests) SIGNED_CERTFILE = data_file("keycert3.pem") +SINGED_CERTFILE_ONLY = data_file("cert3.pem") SIGNED_CERTFILE_HOSTNAME = 'localhost' SIGNED_CERTFILE_INFO = { @@ -4725,6 +4726,13 @@ def test_certificate_chain(self): server_chain=False ) server = ThreadedEchoServer(context=server_context, chatty=False) + + with open(SIGNING_CA) as f: + expected_ca_cert = ssl.PEM_cert_to_DER_cert(f.read()) + + with open(SINGED_CERTFILE_ONLY) as f: + expected_ee_cert = ssl.PEM_cert_to_DER_cert(f.read()) + with server: with client_context.wrap_socket( socket.socket(), @@ -4735,9 +4743,14 @@ def test_certificate_chain(self): self.assertEqual(len(vc), 2) ee, ca = vc + self.assertIsInstance(ee, bytes) + self.assertIsInstance(ca, bytes) + self.assertEqual(expected_ca_cert, ca) + self.assertEqual(expected_ee_cert, ee) uvc = s.get_unverified_chain() self.assertEqual(len(uvc), 1) + self.assertIsInstance(uvc[0], bytes) self.assertEqual(ee, uvc[0]) self.assertNotEqual(ee, ca) 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