]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-31870: Add a timeout parameter to ssl.get_server_certificate() (GH-22270)
authorZackery Spytz <zspytz@gmail.com>
Sat, 24 Apr 2021 04:46:01 +0000 (22:46 -0600)
committerGitHub <noreply@github.com>
Sat, 24 Apr 2021 04:46:01 +0000 (21:46 -0700)
Doc/library/ssl.rst
Doc/whatsnew/3.10.rst
Lib/ssl.py
Lib/test/test_ssl.py
Misc/NEWS.d/next/Library/2020-09-15-23-44-07.bpo-31870.nVwd38.rst [new file with mode: 0644]

index b9e54357bb96909c21fe5688ee7ba291b821a7c8..f7c49dc7c1ea79b600d4373b9b0232a6056c8702 100644 (file)
@@ -426,7 +426,8 @@ Certificate handling
       previously. Return an integer (no fractions of a second in the
       input format)
 
-.. function:: get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, ca_certs=None)
+.. function:: get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, \
+                                     ca_certs=None[, timeout])
 
    Given the address ``addr`` of an SSL-protected server, as a (*hostname*,
    *port-number*) pair, fetches the server's certificate, and returns it as a
@@ -436,7 +437,8 @@ Certificate handling
    same format as used for the same parameter in
    :meth:`SSLContext.wrap_socket`.  The call will attempt to validate the
    server certificate against that set of root certificates, and will fail
-   if the validation attempt fails.
+   if the validation attempt fails.  A timeout can be specified with the
+   ``timeout`` parameter.
 
    .. versionchanged:: 3.3
       This function is now IPv6-compatible.
@@ -445,6 +447,9 @@ Certificate handling
       The default *ssl_version* is changed from :data:`PROTOCOL_SSLv3` to
       :data:`PROTOCOL_TLS` for maximum compatibility with modern servers.
 
+   .. versionchanged:: 3.10
+      The *timeout* parameter was added.
+
 .. function:: DER_cert_to_PEM_cert(DER_cert_bytes)
 
    Given a certificate as a DER-encoded blob of bytes, returns a PEM-encoded
index 247749a48a5f42a0ae20cd10d5e77c5a68f14103..78f3c2d36b845c19f649a159af26dbfb127d4bd5 100644 (file)
@@ -1062,6 +1062,12 @@ The exception :exc:`socket.timeout` is now an alias of :exc:`TimeoutError`.
 Added option to create MPTCP sockets with ``IPPROTO_MPTCP``
 (Contributed by Rui Cunha in :issue:`43571`.)
 
+ssl
+---
+
+Add a *timeout* parameter to the :func:`ssl.get_server_certificate` function.
+(Contributed by Zackery Spytz in :issue:`31870`.)
+
 sys
 ---
 
index 620ddaa93f962c730d565a66193bcf4a2fce9759..2b131de04306acf94a834285087454b6957f27c2 100644 (file)
@@ -258,7 +258,7 @@ if sys.platform == "win32":
     from _ssl import enum_certificates, enum_crls
 
 from socket import socket, SOCK_STREAM, create_connection
-from socket import SOL_SOCKET, SO_TYPE
+from socket import SOL_SOCKET, SO_TYPE, _GLOBAL_DEFAULT_TIMEOUT
 import socket as _socket
 import base64        # for DER-to-PEM translation
 import errno
@@ -1500,11 +1500,14 @@ def PEM_cert_to_DER_cert(pem_cert_string):
     d = pem_cert_string.strip()[len(PEM_HEADER):-len(PEM_FOOTER)]
     return base64.decodebytes(d.encode('ASCII', 'strict'))
 
-def get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, ca_certs=None):
+def get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT,
+                           ca_certs=None, timeout=_GLOBAL_DEFAULT_TIMEOUT):
     """Retrieve the certificate from the server at the specified address,
     and return it as a PEM-encoded string.
     If 'ca_certs' is specified, validate the server cert against it.
-    If 'ssl_version' is specified, use it in the connection attempt."""
+    If 'ssl_version' is specified, use it in the connection attempt.
+    If 'timeout' is specified, use it in the connection attempt.
+    """
 
     host, port = addr
     if ca_certs is not None:
@@ -1514,7 +1517,7 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, ca_certs=None)
     context = _create_stdlib_context(ssl_version,
                                      cert_reqs=cert_reqs,
                                      cafile=ca_certs)
-    with  create_connection(addr) as sock:
+    with create_connection(addr, timeout=timeout) as sock:
         with context.wrap_socket(sock, server_hostname=host) as sslsock:
             dercert = sslsock.getpeercert(True)
     return DER_cert_to_PEM_cert(dercert)
index e2d0def9857675e342ef4828025584602a3b2bc0..327a550645b4f01ebbd1ec27de4e6cfbf887364d 100644 (file)
@@ -2136,6 +2136,11 @@ class SimpleBackgroundTests(unittest.TestCase):
         # independent test method
         _test_get_server_certificate_fail(self, *self.server_addr)
 
+    def test_get_server_certificate_timeout(self):
+        with self.assertRaises(socket.timeout):
+            ssl.get_server_certificate(self.server_addr, ca_certs=SIGNING_CA,
+                                       timeout=0.0001)
+
     def test_ciphers(self):
         with test_wrap_socket(socket.socket(socket.AF_INET),
                              cert_reqs=ssl.CERT_NONE, ciphers="ALL") as s:
diff --git a/Misc/NEWS.d/next/Library/2020-09-15-23-44-07.bpo-31870.nVwd38.rst b/Misc/NEWS.d/next/Library/2020-09-15-23-44-07.bpo-31870.nVwd38.rst
new file mode 100644 (file)
index 0000000..6adf456
--- /dev/null
@@ -0,0 +1,2 @@
+The :func:`ssl.get_server_certificate` function now has a *timeout*
+parameter.