Skip to content

Commit 1bfb112

Browse files
committed
Stub implementation of SNI callback
1 parent 187fd29 commit 1bfb112

File tree

1 file changed

+64
-1
lines changed

1 file changed

+64
-1
lines changed

pep543/stdlib.py

+64-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import ssl
1010
import tempfile
11+
import weakref
1112

1213
from . import (
1314
Backend,
@@ -17,6 +18,7 @@
1718
NextProtocol,
1819
PrivateKey,
1920
ServerContext,
21+
TLSConfiguration,
2022
TLSError,
2123
TLSVersion,
2224
TLSWrappedBuffer,
@@ -71,6 +73,51 @@
7173
del ctx
7274

7375

76+
# This is a mapping of concrete SSLObject objects to the TLSWrappedBuffers
77+
# implementation used here. We use this to get the thing we need in the SNI
78+
# callback. The weakrefs are used to avoid keeping TLSWrappedBuffers objects
79+
# alive unnecessarily.
80+
_object_to_buffer_map = weakref.WeakValueDictionary()
81+
82+
83+
def _sni_callback_builder(user_callback, original_config):
84+
"""
85+
This function returns a closure that includes the PEP 543 SNI callback we
86+
want to call. The closure is the "wrapping" SNI callback, which we use to
87+
translate from the stdlib's callback to the PEP 543 callback form.
88+
89+
Thanks to the immutability we can also close over the original context used
90+
to add this SNI callback.
91+
"""
92+
# This shortcut ensures that if there is no SNI callback to set, we unset
93+
# it from the context, just to be sure.
94+
if user_callback is None:
95+
return None
96+
97+
def pep543_callback(ssl_obj, servername, stdlib_context):
98+
buffer = _object_to_buffer_map[ssl_obj]
99+
100+
# TODO: Are exceptions sensibly propagated here?
101+
new_config = user_callback(buffer, servername, original_config)
102+
103+
# Not returning a TLSConfiguration is an error.
104+
if not isinstance(new_config, TLSConfiguration):
105+
return ssl.ALERT_DESCRIPTION_INTERNAL_ERROR
106+
107+
# Returning an identical configuration to the one that was passed in
108+
# means do nothing. If it's different we need to create a new
109+
# SSLContext and pass it in.
110+
if new_config != original_config:
111+
new_ctx = _init_context(new_config)
112+
ssl_obj.context = new_ctx
113+
114+
# Returning None, perversely, is how one signals success from this
115+
# function. Will wonders never cease?
116+
return None
117+
118+
return pep543_callback
119+
120+
74121
@contextmanager
75122
def _error_converter(ignore_filter=()):
76123
"""
@@ -189,6 +236,17 @@ def _configure_context_for_negotiation(context, inner_protocols):
189236
return context
190237

191238

239+
def _configure_context_for_sni(context, sni_callback, original_config):
240+
"""
241+
Given a PEP 543 SNI callback, configures the SSLContext to actually call
242+
it.
243+
"""
244+
context.set_servername_callback(
245+
_sni_callback_builder(sni_callback, original_config)
246+
)
247+
return context
248+
249+
192250
def _init_context(config):
193251
"""
194252
Initialize an ssl.SSLContext object with a given configuration.
@@ -212,7 +270,9 @@ def _init_context(config):
212270
config.lowest_supported_version,
213271
config.highest_supported_version,
214272
)
215-
# TODO: Add ServerNameCallback
273+
some_context = _configure_context_for_sni(
274+
some_context, config.sni_callback, config
275+
)
216276
return some_context
217277

218278

@@ -239,6 +299,9 @@ def __init__(self, parent_context, ssl_context, server_hostname):
239299
server_hostname=server_hostname
240300
)
241301

302+
# Keep track of the fact that we are the owner of this object.
303+
_object_to_buffer_map[self._object] = self
304+
242305
# We need to track whether the connection is established to properly
243306
# report the TLS version. This is to work around a Python bug:
244307
# https://bugs.python.org/issue29781

0 commit comments

Comments
 (0)