From f700837f4b57ef09600065cfd3a37f9120f746d0 Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Tue, 15 Sep 2020 23:44:34 -0600 Subject: [PATCH] bpo-31870: Add a timeout parameter to ssl.get_server_certificate() --- Doc/library/ssl.rst | 9 +++++++-- Doc/whatsnew/3.10.rst | 6 ++++++ Lib/ssl.py | 11 +++++++---- Lib/test/test_ssl.py | 5 +++++ .../Library/2020-09-15-23-44-07.bpo-31870.nVwd38.rst | 2 ++ 5 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-09-15-23-44-07.bpo-31870.nVwd38.rst diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 1cfd165202d0ef..1d5072aa13a49b 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -425,7 +425,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, ca_certs=None) +.. function:: get_server_certificate(addr, ssl_version=PROTOCOL_TLS, \ + 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 @@ -435,7 +436,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. @@ -444,6 +446,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 diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index f6f276a8bfa495..42ad5e1cee83dd 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -138,6 +138,12 @@ py_compile Added ``--quiet`` option to command-line interface of :mod:`py_compile`. (Contributed by Gregory Schevchenko in :issue:`38731`.) +ssl +--- + +Add a *timeout* parameter to the :func:`ssl.get_server_certificate` function. +(Contributed by Zackery Spytz in :issue:`31870`.) + sys --- diff --git a/Lib/ssl.py b/Lib/ssl.py index 30f4e5934febf9..ebeb8fe6dd4bec 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -253,7 +253,7 @@ class _TLSMessageType(_IntEnum): 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 @@ -1466,11 +1466,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, ca_certs=None): +def get_server_certificate(addr, ssl_version=PROTOCOL_TLS, 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: @@ -1480,7 +1483,7 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_TLS, 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) as sslsock: dercert = sslsock.getpeercert(True) return DER_cert_to_PEM_cert(dercert) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 26eec969a82e0d..7b338dbf4de5a6 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -2134,6 +2134,11 @@ def test_get_server_certificate_fail(self): # 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 index 00000000000000..6adf456d2d6cc9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-09-15-23-44-07.bpo-31870.nVwd38.rst @@ -0,0 +1,2 @@ +The :func:`ssl.get_server_certificate` function now has a *timeout* +parameter.