Skip to content

Commit

Permalink
bpo-36076: Add SNI support to ssl.get_server_certificate. (pythonGH-1…
Browse files Browse the repository at this point in the history
…6820)

Many servers in the cloud environment require SNI to be used during the
SSL/TLS handshake, therefore it is not possible to fetch their certificates
using the ssl.get_server_certificate interface.

This change adds an additional optional hostname argument that can be used to
set the SNI. Note that it is intentionally a separate argument instead of
using the host part of the addr tuple, because one might want to explicitly
fetch the default certificate or fetch a certificate from a specific IP
address with the specified SNI hostname. A separate argument also works better
for backwards compatibility.

Automerge-Triggered-By: GH:tiran
  • Loading branch information
juhovh authored Apr 18, 2021
1 parent 2798f24 commit 49fdf11
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Lib/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1475,7 +1475,7 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_TLS, ca_certs=None):
cert_reqs=cert_reqs,
cafile=ca_certs)
with create_connection(addr) as sock:
with context.wrap_socket(sock) as sslsock:
with context.wrap_socket(sock, server_hostname=host) as sslsock:
dercert = sslsock.getpeercert(True)
return DER_cert_to_PEM_cert(dercert)

Expand Down
26 changes: 25 additions & 1 deletion Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1962,7 +1962,9 @@ class SimpleBackgroundTests(unittest.TestCase):
"""Tests that connect to a simple server running in the background"""

def setUp(self):
server = ThreadedEchoServer(SIGNED_CERTFILE)
self.server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
self.server_context.load_cert_chain(SIGNED_CERTFILE)
server = ThreadedEchoServer(context=self.server_context)
self.server_addr = (HOST, server.port)
server.__enter__()
self.addCleanup(server.__exit__, None, None, None)
Expand Down Expand Up @@ -2143,6 +2145,28 @@ def test_non_blocking_handshake(self):
def test_get_server_certificate(self):
_test_get_server_certificate(self, *self.server_addr, cert=SIGNING_CA)

@needs_sni
def test_get_server_certificate_sni(self):
host, port = self.server_addr
server_names = []

# We store servername_cb arguments to make sure they match the host
def servername_cb(ssl_sock, server_name, initial_context):
server_names.append(server_name)
self.server_context.set_servername_callback(servername_cb)

pem = ssl.get_server_certificate((host, port))
if not pem:
self.fail("No server certificate on %s:%s!" % (host, port))

pem = ssl.get_server_certificate((host, port), ca_certs=SIGNING_CA)
if not pem:
self.fail("No server certificate on %s:%s!" % (host, port))
if support.verbose:
sys.stdout.write("\nVerified certificate for %s:%s is\n%s\n" % (host, port, pem))

self.assertEqual(server_names, [host, host])

def test_get_server_certificate_fail(self):
# Connection failure crashes ThreadedEchoServer, so run this in an
# independent test method
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1803,6 +1803,7 @@ Michael Urman
Hector Urtubia
Elizabeth Uselton
Lukas Vacek
Juho Vähä-Herttua
Ville Vainio
Yann Vaginay
Andi Vajda
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added SNI support to :func:`ssl.get_server_certificate`.

0 comments on commit 49fdf11

Please sign in to comment.