.. versionadded:: 3.2
+ .. versionchanged:: 3.7
+ The attribute is now always ASCII text. When ``server_hostname`` is
+ an internationalized domain name (IDN), this attribute now stores the
+ A-label form (``"xn--pythn-mua.org"``), rather than the U-label form
+ (``"pythön.org"``).
+
.. attribute:: SSLSocket.session
The :class:`SSLSession` for this SSL connection. The session is available
.. versionadded:: 3.3
-.. method:: SSLContext.set_servername_callback(server_name_callback)
+.. attribute:: SSLContext.sni_callback
Register a callback function that will be called after the TLS Client Hello
handshake message has been received by the SSL/TLS server when the TLS client
specifies a server name indication. The server name indication mechanism
is specified in :rfc:`6066` section 3 - Server Name Indication.
- Only one callback can be set per ``SSLContext``. If *server_name_callback*
- is ``None`` then the callback is disabled. Calling this function a
+ Only one callback can be set per ``SSLContext``. If *sni_callback*
+ is set to ``None`` then the callback is disabled. Calling this function a
subsequent time will disable the previously registered callback.
- The callback function, *server_name_callback*, will be called with three
+ The callback function will be called with three
arguments; the first being the :class:`ssl.SSLSocket`, the second is a string
that represents the server name that the client is intending to communicate
(or :const:`None` if the TLS Client Hello does not contain a server name)
and the third argument is the original :class:`SSLContext`. The server name
- argument is the IDNA decoded server name.
+ argument is text. For internationalized domain name, the server
+ name is an IDN A-label (``"xn--pythn-mua.org"``).
A typical use of this callback is to change the :class:`ssl.SSLSocket`'s
:attr:`SSLSocket.context` attribute to a new object of type
the TLS connection has progressed beyond the TLS Client Hello and therefore
will not contain return meaningful values nor can they be called safely.
- The *server_name_callback* function must return ``None`` to allow the
+ The *sni_callback* function must return ``None`` to allow the
TLS negotiation to continue. If a TLS failure is required, a constant
:const:`ALERT_DESCRIPTION_* <ALERT_DESCRIPTION_INTERNAL_ERROR>` can be
returned. Other return values will result in a TLS fatal error with
:const:`ALERT_DESCRIPTION_INTERNAL_ERROR`.
- If there is an IDNA decoding error on the server name, the TLS connection
- will terminate with an :const:`ALERT_DESCRIPTION_INTERNAL_ERROR` fatal TLS
- alert message to the client.
-
- If an exception is raised from the *server_name_callback* function the TLS
+ If an exception is raised from the *sni_callback* function the TLS
connection will terminate with a fatal TLS alert message
:const:`ALERT_DESCRIPTION_HANDSHAKE_FAILURE`.
This method will raise :exc:`NotImplementedError` if the OpenSSL library
had OPENSSL_NO_TLSEXT defined when it was built.
+ .. versionadded:: 3.7
+
+.. attribute:: SSLContext.set_servername_callback(server_name_callback)
+
+ This is a legacy API retained for backwards compatibility. When possible,
+ you should use :attr:`sni_callback` instead. The given *server_name_callback*
+ is similar to *sni_callback*, except that when the server hostname is an
+ IDN-encoded internationalized domain name, the *server_name_callback*
+ receives a decoded U-label (``"pythön.org"``).
+
+ If there is an decoding error on the server name, the TLS connection will
+ terminate with an :const:`ALERT_DESCRIPTION_INTERNAL_ERROR` fatal TLS
+ alert message to the client.
+
.. versionadded:: 3.4
.. method:: SSLContext.load_dh_params(dhfile)
suite selection can be configured on compile time.
(Contributed by Christian Heimes in :issue:`31429`.)
+Added support for validating server certificates containing
+internationalized domain names (IDNs). As part of this change, the
+:attr:`ssl.SSLSocket.server_hostname` attribute now stores the
+expected hostname in A-label form (``"xn--pythn-mua.org"``), rather
+than the U-label form (``"pythön.org"``). (Contributed by
+Nathaniel J. Smith and Christian Heimes in :issue:`28414`.)
+
+
string
------
self = _SSLContext.__new__(cls, protocol)
return self
- def __init__(self, protocol=PROTOCOL_TLS):
- self.protocol = protocol
+ def _encode_hostname(self, hostname):
+ if hostname is None:
+ return None
+ elif isinstance(hostname, str):
+ return hostname.encode('idna').decode('ascii')
+ else:
+ return hostname.decode('ascii')
def wrap_socket(self, sock, server_side=False,
do_handshake_on_connect=True,
suppress_ragged_eofs=True,
server_hostname=None, session=None):
+ # SSLSocket class handles server_hostname encoding before it calls
+ # ctx._wrap_socket()
return self.sslsocket_class(
sock=sock,
server_side=server_side,
def wrap_bio(self, incoming, outgoing, server_side=False,
server_hostname=None, session=None):
- sslobj = self._wrap_bio(incoming, outgoing, server_side=server_side,
- server_hostname=server_hostname)
+ # Need to encode server_hostname here because _wrap_bio() can only
+ # handle ASCII str.
+ sslobj = self._wrap_bio(
+ incoming, outgoing, server_side=server_side,
+ server_hostname=self._encode_hostname(server_hostname)
+ )
return self.sslobject_class(sslobj, session=session)
def set_npn_protocols(self, npn_protocols):
self._set_npn_protocols(protos)
+ def set_servername_callback(self, server_name_callback):
+ if server_name_callback is None:
+ self.sni_callback = None
+ else:
+ if not callable(server_name_callback):
+ raise TypeError("not a callable object")
+
+ def shim_cb(sslobj, servername, sslctx):
+ servername = self._encode_hostname(servername)
+ return server_name_callback(sslobj, servername, sslctx)
+
+ self.sni_callback = shim_cb
+
def set_alpn_protocols(self, alpn_protocols):
protos = bytearray()
for protocol in alpn_protocols:
def hostname_checks_common_name(self):
return True
+ @property
+ def protocol(self):
+ return _SSLMethod(super().protocol)
+
@property
def verify_flags(self):
return VerifyFlags(super().verify_flags)
raise ValueError("check_hostname requires server_hostname")
self._session = _session
self.server_side = server_side
- self.server_hostname = server_hostname
+ self.server_hostname = self._context._encode_hostname(server_hostname)
self.do_handshake_on_connect = do_handshake_on_connect
self.suppress_ragged_eofs = suppress_ragged_eofs
if sock is not None:
# create the SSL object
try:
sslobj = self._context._wrap_socket(self, server_side,
- server_hostname)
+ self.server_hostname)
self._sslobj = SSLObject(sslobj, owner=self,
session=self._session)
if do_handshake_on_connect:
# For compatibility
self.assertEqual(cm.exception.errno, ssl.SSL_ERROR_WANT_READ)
- def test_bad_idna_in_server_hostname(self):
- # Note: this test is testing some code that probably shouldn't exist
- # in the first place, so if it starts failing at some point because
- # you made the ssl module stop doing IDNA decoding then please feel
- # free to remove it. The test was mainly added because this case used
- # to cause memory corruption (see bpo-30594).
- ctx = ssl.create_default_context()
- with self.assertRaises(UnicodeError):
- ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO(),
- server_hostname="xn--.com")
def test_bad_server_hostname(self):
ctx = ssl.create_default_context()
if support.verbose:
sys.stdout.write("\n")
- server_context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+ server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_context.load_cert_chain(IDNSANSFILE)
- context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
context.load_verify_locations(SIGNING_CA)
# different ways
idn_hostnames = [
('könig.idn.pythontest.net',
- 'könig.idn.pythontest.net',),
+ 'xn--knig-5qa.idn.pythontest.net'),
('xn--knig-5qa.idn.pythontest.net',
'xn--knig-5qa.idn.pythontest.net'),
(b'xn--knig-5qa.idn.pythontest.net',
- b'xn--knig-5qa.idn.pythontest.net'),
+ 'xn--knig-5qa.idn.pythontest.net'),
('königsgäßchen.idna2003.pythontest.net',
- 'königsgäßchen.idna2003.pythontest.net'),
+ 'xn--knigsgsschen-lcb0w.idna2003.pythontest.net'),
('xn--knigsgsschen-lcb0w.idna2003.pythontest.net',
'xn--knigsgsschen-lcb0w.idna2003.pythontest.net'),
(b'xn--knigsgsschen-lcb0w.idna2003.pythontest.net',
- b'xn--knigsgsschen-lcb0w.idna2003.pythontest.net'),
+ 'xn--knigsgsschen-lcb0w.idna2003.pythontest.net'),
+
+ # ('königsgäßchen.idna2008.pythontest.net',
+ # 'xn--knigsgchen-b4a3dun.idna2008.pythontest.net'),
+ ('xn--knigsgchen-b4a3dun.idna2008.pythontest.net',
+ 'xn--knigsgchen-b4a3dun.idna2008.pythontest.net'),
+ (b'xn--knigsgchen-b4a3dun.idna2008.pythontest.net',
+ 'xn--knigsgchen-b4a3dun.idna2008.pythontest.net'),
+
]
for server_hostname, expected_hostname in idn_hostnames:
server = ThreadedEchoServer(context=server_context, chatty=True)
s.getpeercert()
self.assertEqual(s.server_hostname, expected_hostname)
- # bug https://bugs.python.org/issue28414
- # IDNA 2008 deviations are broken
- idna2008 = 'xn--knigsgchen-b4a3dun.idna2008.pythontest.net'
- server = ThreadedEchoServer(context=server_context, chatty=True)
- with server:
- with self.assertRaises(UnicodeError):
- with context.wrap_socket(socket.socket(),
- server_hostname=idna2008) as s:
- s.connect((HOST, server.port))
-
# incorrect hostname should raise an exception
server = ThreadedEchoServer(context=server_context, chatty=True)
with server:
--- /dev/null
+The ssl module now allows users to perform their own IDN en/decoding when using SNI.
unsigned int alpn_protocols_len;
#endif
#ifndef OPENSSL_NO_TLSEXT
- PyObject *set_hostname;
+ PyObject *set_sni_cb;
#endif
int check_hostname;
/* OpenSSL has no API to get hostflags from X509_VERIFY_PARAM* struct.
* We have to maintain our own copy. OpenSSL's hostflags default to 0.
*/
unsigned int hostflags;
+ int protocol;
} PySSLContext;
typedef struct {
static int PySSL_select(PySocketSockObject *s, int writing, _PyTime_t timeout);
-
-#define PySSLContext_Check(v) (Py_TYPE(v) == &PySSLContext_Type)
#define PySSLSocket_Check(v) (Py_TYPE(v) == &PySSLSocket_Type)
#define PySSLMemoryBIO_Check(v) (Py_TYPE(v) == &PySSLMemoryBIO_Type)
#define PySSLSession_Check(v) (Py_TYPE(v) == &PySSLSession_Type)
ERR_clear_error();
}
- hostname = PyUnicode_Decode(server_hostname, len, "idna", "strict");
+ hostname = PyUnicode_Decode(server_hostname, len, "ascii", "strict");
if (hostname == NULL) {
goto error;
}
"_setter_context(ctx)\n\
\
This changes the context associated with the SSLSocket. This is typically\n\
-used from within a callback function set by the set_servername_callback\n\
+used from within a callback function set by the sni_callback\n\
on the SSLContext to change the certificate information associated with the\n\
SSLSocket before the cryptographic exchange handshake messages\n");
}
self->ctx = ctx;
self->hostflags = X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
+ self->protocol = proto_version;
#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
self->npn_protocols = NULL;
#endif
self->alpn_protocols = NULL;
#endif
#ifndef OPENSSL_NO_TLSEXT
- self->set_hostname = NULL;
+ self->set_sni_cb = NULL;
#endif
/* Don't check host name by default */
if (proto_version == PY_SSL_VERSION_TLS_CLIENT) {
context_traverse(PySSLContext *self, visitproc visit, void *arg)
{
#ifndef OPENSSL_NO_TLSEXT
- Py_VISIT(self->set_hostname);
+ Py_VISIT(self->set_sni_cb);
#endif
return 0;
}
context_clear(PySSLContext *self)
{
#ifndef OPENSSL_NO_TLSEXT
- Py_CLEAR(self->set_hostname);
+ Py_CLEAR(self->set_sni_cb);
#endif
return 0;
}
return 0;
}
+static PyObject *
+get_protocol(PySSLContext *self, void *c) {
+ return PyLong_FromLong(self->protocol);
+}
typedef struct {
PyThreadState *thread_state;
PyObject *res;
/* server_hostname is either None (or absent), or to be encoded
- using the idna encoding. */
+ as IDN A-label (ASCII str). */
if (hostname_obj != Py_None) {
- if (!PyArg_Parse(hostname_obj, "et", "idna", &hostname))
+ if (!PyArg_Parse(hostname_obj, "es", "ascii", &hostname))
return NULL;
}
PyObject *res;
/* server_hostname is either None (or absent), or to be encoded
- using the idna encoding. */
+ as IDN A-label (ASCII str). */
if (hostname_obj != Py_None) {
- if (!PyArg_Parse(hostname_obj, "et", "idna", &hostname))
+ if (!PyArg_Parse(hostname_obj, "es", "ascii", &hostname))
return NULL;
}
int ret;
PySSLContext *ssl_ctx = (PySSLContext *) args;
PySSLSocket *ssl;
- PyObject *servername_o;
- PyObject *servername_idna;
PyObject *result;
/* The high-level ssl.SSLSocket object */
PyObject *ssl_socket;
const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
PyGILState_STATE gstate = PyGILState_Ensure();
- if (ssl_ctx->set_hostname == NULL) {
+ if (ssl_ctx->set_sni_cb == NULL) {
/* remove race condition in this the call back while if removing the
* callback is in progress */
PyGILState_Release(gstate);
goto error;
if (servername == NULL) {
- result = PyObject_CallFunctionObjArgs(ssl_ctx->set_hostname, ssl_socket,
+ result = PyObject_CallFunctionObjArgs(ssl_ctx->set_sni_cb, ssl_socket,
Py_None, ssl_ctx, NULL);
}
else {
- servername_o = PyBytes_FromString(servername);
- if (servername_o == NULL) {
+ PyObject *servername_bytes;
+ PyObject *servername_str;
+
+ servername_bytes = PyBytes_FromString(servername);
+ if (servername_bytes == NULL) {
PyErr_WriteUnraisable((PyObject *) ssl_ctx);
goto error;
}
- servername_idna = PyUnicode_FromEncodedObject(servername_o, "idna", NULL);
- if (servername_idna == NULL) {
- PyErr_WriteUnraisable(servername_o);
- Py_DECREF(servername_o);
+ /* server_hostname was encoded to an A-label by our caller; put it
+ * back into a str object, but still as an A-label (bpo-28414)
+ */
+ servername_str = PyUnicode_FromEncodedObject(servername_bytes, "ascii", NULL);
+ Py_DECREF(servername_bytes);
+ if (servername_str == NULL) {
+ PyErr_WriteUnraisable(servername_bytes);
goto error;
}
- Py_DECREF(servername_o);
- result = PyObject_CallFunctionObjArgs(ssl_ctx->set_hostname, ssl_socket,
- servername_idna, ssl_ctx, NULL);
- Py_DECREF(servername_idna);
+ result = PyObject_CallFunctionObjArgs(
+ ssl_ctx->set_sni_cb, ssl_socket, servername_str,
+ ssl_ctx, NULL);
+ Py_DECREF(servername_str);
}
Py_DECREF(ssl_socket);
if (result == NULL) {
- PyErr_WriteUnraisable(ssl_ctx->set_hostname);
+ PyErr_WriteUnraisable(ssl_ctx->set_sni_cb);
*al = SSL_AD_HANDSHAKE_FAILURE;
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
}
else {
- if (result != Py_None) {
+ /* Result may be None, a SSLContext or an integer
+ * None and SSLContext are OK, integer or other values are an error.
+ */
+ if (result == Py_None) {
+ ret = SSL_TLSEXT_ERR_OK;
+ } else {
*al = (int) PyLong_AsLong(result);
if (PyErr_Occurred()) {
PyErr_WriteUnraisable(result);
}
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
}
- else {
- ret = SSL_TLSEXT_ERR_OK;
- }
Py_DECREF(result);
}
}
#endif
-/*[clinic input]
-_ssl._SSLContext.set_servername_callback
- method as cb: object
- /
-
-Set a callback that will be called when a server name is provided by the SSL/TLS client in the SNI extension.
-
-If the argument is None then the callback is disabled. The method is called
-with the SSLSocket, the server name as a string, and the SSLContext object.
-See RFC 6066 for details of the SNI extension.
-[clinic start generated code]*/
-
static PyObject *
-_ssl__SSLContext_set_servername_callback(PySSLContext *self, PyObject *cb)
-/*[clinic end generated code: output=3439a1b2d5d3b7ea input=a2a83620197d602b]*/
+get_sni_callback(PySSLContext *self, void *c)
{
+ PyObject *cb = self->set_sni_cb;
+ if (cb == NULL) {
+ Py_RETURN_NONE;
+ }
+ Py_INCREF(cb);
+ return cb;
+}
+
+static int
+set_sni_callback(PySSLContext *self, PyObject *arg, void *c)
+{
+ if (self->protocol == PY_SSL_VERSION_TLS_CLIENT) {
+ PyErr_SetString(PyExc_ValueError,
+ "sni_callback cannot be set on TLS_CLIENT context");
+ return -1;
+ }
#if HAVE_SNI && !defined(OPENSSL_NO_TLSEXT)
- Py_CLEAR(self->set_hostname);
- if (cb == Py_None) {
+ Py_CLEAR(self->set_sni_cb);
+ if (arg == Py_None) {
SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL);
}
else {
- if (!PyCallable_Check(cb)) {
+ if (!PyCallable_Check(arg)) {
SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL);
PyErr_SetString(PyExc_TypeError,
"not a callable object");
- return NULL;
+ return -1;
}
- Py_INCREF(cb);
- self->set_hostname = cb;
+ Py_INCREF(arg);
+ self->set_sni_cb = arg;
SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback);
SSL_CTX_set_tlsext_servername_arg(self->ctx, self);
}
- Py_RETURN_NONE;
+ return 0;
#else
PyErr_SetString(PyExc_NotImplementedError,
"The TLS extension servername callback, "
"SSL_CTX_set_tlsext_servername_callback, "
"is not in the current OpenSSL library.");
- return NULL;
+ return -1;
#endif
}
+PyDoc_STRVAR(PySSLContext_sni_callback_doc,
+"Set a callback that will be called when a server name is provided by the SSL/TLS client in the SNI extension.\n\
+\n\
+If the argument is None then the callback is disabled. The method is called\n\
+with the SSLSocket, the server name as a string, and the SSLContext object.\n\
+See RFC 6066 for details of the SNI extension.");
+
/*[clinic input]
_ssl._SSLContext.cert_store_stats
(setter) set_check_hostname, NULL},
{"_host_flags", (getter) get_host_flags,
(setter) set_host_flags, NULL},
+ {"sni_callback", (getter) get_sni_callback,
+ (setter) set_sni_callback, PySSLContext_sni_callback_doc},
{"options", (getter) get_options,
(setter) set_options, NULL},
+ {"protocol", (getter) get_protocol,
+ NULL, NULL},
{"verify_flags", (getter) get_verify_flags,
(setter) set_verify_flags, NULL},
{"verify_mode", (getter) get_verify_mode,
_SSL__SSLCONTEXT_SESSION_STATS_METHODDEF
_SSL__SSLCONTEXT_SET_DEFAULT_VERIFY_PATHS_METHODDEF
_SSL__SSLCONTEXT_SET_ECDH_CURVE_METHODDEF
- _SSL__SSLCONTEXT_SET_SERVERNAME_CALLBACK_METHODDEF
_SSL__SSLCONTEXT_CERT_STORE_STATS_METHODDEF
_SSL__SSLCONTEXT_GET_CA_CERTS_METHODDEF
_SSL__SSLCONTEXT_GET_CIPHERS_METHODDEF
#endif /* !defined(OPENSSL_NO_ECDH) */
-PyDoc_STRVAR(_ssl__SSLContext_set_servername_callback__doc__,
-"set_servername_callback($self, method, /)\n"
-"--\n"
-"\n"
-"Set a callback that will be called when a server name is provided by the SSL/TLS client in the SNI extension.\n"
-"\n"
-"If the argument is None then the callback is disabled. The method is called\n"
-"with the SSLSocket, the server name as a string, and the SSLContext object.\n"
-"See RFC 6066 for details of the SNI extension.");
-
-#define _SSL__SSLCONTEXT_SET_SERVERNAME_CALLBACK_METHODDEF \
- {"set_servername_callback", (PyCFunction)_ssl__SSLContext_set_servername_callback, METH_O, _ssl__SSLContext_set_servername_callback__doc__},
-
PyDoc_STRVAR(_ssl__SSLContext_cert_store_stats__doc__,
"cert_store_stats($self, /)\n"
"--\n"
#ifndef _SSL_ENUM_CRLS_METHODDEF
#define _SSL_ENUM_CRLS_METHODDEF
#endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */
-/*[clinic end generated code: output=3d42305ed0ad162a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=84e1fd89aff9b0f7 input=a9049054013a1b77]*/