From 9d0f6dc2634a044e93ecac0fdf2ce007b8bad4bb Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Thu, 30 Mar 2023 12:15:43 +1300 Subject: [PATCH 01/18] gh-63284: Add support for TLS-PSK (pre-shared key) to the ssl module --- Doc/library/ssl.rst | 74 +++++++ .../pycore_global_objects_fini_generated.h | 2 + Include/internal/pycore_global_strings.h | 2 + .../internal/pycore_runtime_init_generated.h | 2 + .../internal/pycore_unicodeobject_generated.h | 6 + Lib/test/test_ssl.py | 56 +++++ Misc/ACKS | 1 + ...3-04-02-11-40-59.gh-issue-63284.oMYuon.rst | 1 + Modules/_ssl.c | 202 ++++++++++++++++++ Modules/clinic/_ssl.c.h | 137 +++++++++++- 10 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 4b60b7c643b62c..bd1713890198b7 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1987,6 +1987,80 @@ to speed up repeated connections from the same clients. >>> ssl.create_default_context().verify_mode # doctest: +SKIP +.. method:: SSLContext.set_psk_client_callback(callback) + + Enables TLS-PSK (pre-shared key) authentication on a client-side connection. + + In general, certificate based authentication should be preferred over this method. + + The parameter ``callback`` is a callable object with the signature: + ``def callback(hint: str | None) -> tuple[str | None, bytes]``. + The ``hint`` parameter is an optional identity hint sent by the server. + The return value is a tuple in the form (client-identity, psk). + Client-identity is an optional string which may be used by the server to + select a corresponding PSK for the client. PSK is a + :term:`bytes-like object` representing the pre-shared key. + + Setting ``callback`` to :const:`None` removes any existing callback. + + Example usage:: + + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + context.maximum_version = ssl.TLSVersion.TLSv1_2 + context.set_ciphers('PSK') + + # A simple lambda: + psk = bytes.fromhex('deadbeef') + context.set_psk_client_callback(lambda hint: (None, psk)) + + # A table using the hint from the server: + psk_table = { 'ServerId_1': bytes.fromhex('deadbeef'), + 'ServerId_2': bytes.fromhex('cafebabe') + } + def callback(hint): + return 'ClientId_1', psk_table[hint] + context.set_psk_client_callback(callback) + + .. versionadded:: 3.12 + +.. method:: SSLContext.set_psk_server_callback(callback, identity_hint=None) + + Enables TLS-PSK (pre-shared key) authentication on a server-side connection. + + In general, certificate based authentication should be preferred over this method. + + The parameter ``callback`` is a callable object with the signature: + ``def callback(identity: str | None) -> bytes``. + The ``identity`` parameter is an optional identity sent by the client which can + be used to select a corresponding PSK. + The return value is a :term:`bytes-like object` representing the pre-shared key. + + Setting ``callback`` to :const:`None` removes any existing callback. + + The parameter ``identity_hint`` is an optional identity hint sent to the client. + + Example usage:: + + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.maximum_version = ssl.TLSVersion.TLSv1_2 + context.set_ciphers('PSK') + + # A simple lambda: + psk = bytes.fromhex('deadbeef') + context.set_psk_server_callback(lambda identity: psk) + + # A table using the identity of the client: + psk_table = { 'ClientId_1': bytes.fromhex('deadbeef'), + 'ClientId_2': bytes.fromhex('cafed00d') + } + def callback(identity): + return psk_table[identity] + context.set_psk_server_callback(callback, 'ServerId_1') + + .. versionadded:: 3.12 + .. index:: single: certificates .. index:: single: X509 certificate diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 14dfd9ea5823ed..5a95897876b8f5 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -815,6 +815,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(call)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(call_exception_handler)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(call_soon)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(callback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cancel)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(capath)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(category)); @@ -954,6 +955,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hook)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(id)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ident)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(identity_hint)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ignore)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(imag)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(importlib)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 6f430bb25eb8d3..88b29749d5e51f 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -301,6 +301,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(call) STRUCT_FOR_ID(call_exception_handler) STRUCT_FOR_ID(call_soon) + STRUCT_FOR_ID(callback) STRUCT_FOR_ID(cancel) STRUCT_FOR_ID(capath) STRUCT_FOR_ID(category) @@ -440,6 +441,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(hook) STRUCT_FOR_ID(id) STRUCT_FOR_ID(ident) + STRUCT_FOR_ID(identity_hint) STRUCT_FOR_ID(ignore) STRUCT_FOR_ID(imag) STRUCT_FOR_ID(importlib) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 0452c4c61551de..4f3442bf9ed504 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -807,6 +807,7 @@ extern "C" { INIT_ID(call), \ INIT_ID(call_exception_handler), \ INIT_ID(call_soon), \ + INIT_ID(callback), \ INIT_ID(cancel), \ INIT_ID(capath), \ INIT_ID(category), \ @@ -946,6 +947,7 @@ extern "C" { INIT_ID(hook), \ INIT_ID(id), \ INIT_ID(ident), \ + INIT_ID(identity_hint), \ INIT_ID(ignore), \ INIT_ID(imag), \ INIT_ID(importlib), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 7114a5416f2515..a0fefb8b67bdf8 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -756,6 +756,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(call_soon); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(callback); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(cancel); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1173,6 +1176,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(ident); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(identity_hint); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(ignore); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index abf024fb89d2f3..e200cda81f17f5 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4203,6 +4203,62 @@ def test_session_handling(self): self.assertEqual(str(e.exception), 'Session refers to a different SSLContext.') + @requires_tls_version('TLSv1_2') + def test_psk(self): + psk = bytes.fromhex('deadbeef') + + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.check_hostname = False + client_context.verify_mode = ssl.CERT_NONE + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 + client_context.set_ciphers('PSK') + client_context.set_psk_client_callback(lambda hint: (None, psk)) + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + server_context.maximum_version = ssl.TLSVersion.TLSv1_2 + server_context.set_ciphers('PSK') + server_context.set_psk_server_callback(lambda identity: psk) + + # correct PSK should connect + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + + # incorrect PSK should fail + incorrect_psk = bytes.fromhex('cafebabe') + client_context.set_psk_client_callback(lambda hint: (None, incorrect_psk)) + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as s: + with self.assertRaises(ssl.SSLError): + s.connect((HOST, server.port)) + + # identity_hint and client_identity should be sent to the other side + identity_hint = 'identity-hint' + client_identity = 'client-identity' + + def client_callback(hint): + self.assertEqual(hint, identity_hint) + return client_identity, psk + + def server_callback(identity): + self.assertEqual(identity, client_identity) + return psk + + client_context.set_psk_client_callback(client_callback) + server_context.set_psk_server_callback(server_callback, identity_hint) + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + + # adding client callback to server or vice versa raises an exception + with self.assertRaisesRegex(ssl.SSLError, 'Cannot add PSK server callback'): + client_context.set_psk_server_callback(server_callback, identity_hint) + with self.assertRaisesRegex(ssl.SSLError, 'Cannot add PSK client callback'): + server_context.set_psk_client_callback(client_callback) + @unittest.skipUnless(has_tls_version('TLSv1_3'), "Test needs TLS 1.3") class TestPostHandshakeAuth(unittest.TestCase): diff --git a/Misc/ACKS b/Misc/ACKS index 8cf5166a2bb1f4..75df0886f05955 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1466,6 +1466,7 @@ Ajith Ramachandran Dhushyanth Ramasamy Ashwin Ramaswami Jeff Ramnani +Grant Ramsay Bayard Randel Varpu Rantala Brodie Rao diff --git a/Misc/NEWS.d/next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst b/Misc/NEWS.d/next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst new file mode 100644 index 00000000000000..d61f56df68dbe7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst @@ -0,0 +1 @@ +Added support for TLS-PSK (pre-shared key) to the ssl module diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 3fbb37332f67d3..38d27c3473805d 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -294,6 +294,8 @@ typedef struct { BIO *keylog_bio; /* Cached module state, also used in SSLSocket and SSLSession code. */ _sslmodulestate *state; + PyObject *psk_client_callback; + PyObject *psk_server_callback; } PySSLContext; typedef struct { @@ -3088,6 +3090,8 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version) self->alpn_protocols = NULL; self->set_sni_cb = NULL; self->state = get_ssl_state(module); + self->psk_client_callback = NULL; + self->psk_server_callback = NULL; /* Don't check host name by default */ if (proto_version == PY_SSL_VERSION_TLS_CLIENT) { @@ -4614,6 +4618,202 @@ _ssl__SSLContext_get_ca_certs_impl(PySSLContext *self, int binary_form) return NULL; } +static unsigned int psk_client_callback(SSL *s, + const char *hint, + char *identity, + unsigned int max_identity_len, + unsigned char *psk, + unsigned int max_psk_len) +{ + PyGILState_STATE gstate = PyGILState_Ensure(); + + PySSLSocket *ssl = SSL_get_app_data(s); + if (ssl == NULL || ssl->ctx == NULL) { + goto error; + } + PyObject *callback = ssl->ctx->psk_client_callback; + if (callback == NULL) { + goto error; + } + + PyObject *hint_str = (hint != NULL) ? + PyUnicode_DecodeASCII(hint, strlen(hint), "strict") : + Py_NewRef(Py_None); + if (hint_str == NULL) { + goto error; + } + PyObject *result = PyObject_CallFunctionObjArgs(callback, hint_str, NULL); + Py_DECREF(hint_str); + + if (result == NULL) { + goto error; + } + + const char *psk_; + const char *identity_; + Py_ssize_t psk_len_; + Py_ssize_t identity_len_; + if (!PyArg_ParseTuple(result, "z#y#", &identity_, &identity_len_, &psk_, &psk_len_)) { + Py_DECREF(result); + goto error; + } + + if (identity_len_ + 1 > max_identity_len || psk_len_ > max_psk_len) { + Py_DECREF(result); + goto error; + } + memcpy(psk, psk_, psk_len_); + memcpy(identity, identity_, identity_len_); + identity[identity_len_] = 0; + + Py_DECREF(result); + + PyGILState_Release(gstate); + return (unsigned int)psk_len_; + +error: + PyGILState_Release(gstate); + return 0; +} + +/*[clinic input] +_ssl._SSLContext.set_psk_client_callback + callback: object + +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLContext_set_psk_client_callback_impl(PySSLContext *self, + PyObject *callback) +/*[clinic end generated code: output=0aba86f6ed75119e input=7627bae0e5ee7635]*/ +{ + if (self->protocol == PY_SSL_VERSION_TLS_SERVER) { + _setSSLError(get_state_ctx(self), + "Cannot add PSK client callback to a " + "PROTOCOL_TLS_SERVER context", 0, __FILE__, __LINE__); + return NULL; + } + + SSL_psk_client_cb_func ssl_callback; + if (callback == Py_None) { + callback = NULL; + // Delete the existing callback + ssl_callback = NULL; + } else { + if (!PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "callback must be callable"); + return NULL; + } + Py_INCREF(callback); + ssl_callback = psk_client_callback; + } + + if (self->psk_client_callback != NULL) { + Py_DECREF(self->psk_client_callback); + } + self->psk_client_callback = callback; + SSL_CTX_set_psk_client_callback(self->ctx, ssl_callback); + + Py_RETURN_NONE; +} + +static unsigned int psk_server_callback(SSL *s, + const char *identity, + unsigned char *psk, + unsigned int max_psk_len) +{ + PyGILState_STATE gstate = PyGILState_Ensure(); + + PySSLSocket *ssl = SSL_get_app_data(s); + if (ssl == NULL || ssl->ctx == NULL) { + goto error; + } + PyObject *callback = ssl->ctx->psk_server_callback; + if (callback == NULL) { + goto error; + } + + PyObject *identity_str = (identity[0] != '\0') ? + PyUnicode_DecodeASCII(identity, strlen(identity), "strict") : + Py_NewRef(Py_None); + if (identity_str == NULL) { + goto error; + } + PyObject *result = PyObject_CallFunctionObjArgs(callback, identity_str, NULL); + Py_DECREF(identity_str); + + if (result == NULL) { + goto error; + } + + const char *psk_; + Py_ssize_t psk_len_; + if (!PyArg_Parse(result, "y#", &psk_, &psk_len_)) { + Py_DECREF(result); + goto error; + } + + if (psk_len_ > max_psk_len) { + Py_DECREF(result); + goto error; + } + memcpy(psk, psk_, psk_len_); + + Py_DECREF(result); + + PyGILState_Release(gstate); + return (unsigned int)psk_len_; + +error: + PyGILState_Release(gstate); + return 0; +} + +/*[clinic input] +_ssl._SSLContext.set_psk_server_callback + callback: object + identity_hint: str(accept={str, NoneType}) = None + +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLContext_set_psk_server_callback_impl(PySSLContext *self, + PyObject *callback, + const char *identity_hint) +/*[clinic end generated code: output=1f4d6a4e09a92b03 input=65d4b6022aa85ea3]*/ +{ + if (self->protocol == PY_SSL_VERSION_TLS_CLIENT) { + _setSSLError(get_state_ctx(self), + "Cannot add PSK server callback to a " + "PROTOCOL_TLS_CLIENT context", 0, __FILE__, __LINE__); + return NULL; + } + + SSL_psk_server_cb_func ssl_callback; + if (callback == Py_None) { + callback = NULL; + // Delete the existing callback and hint + ssl_callback = NULL; + identity_hint = NULL; + } else { + if (!PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "callback must be callable"); + return NULL; + } + Py_INCREF(callback); + ssl_callback = psk_server_callback; + } + + if (self->psk_server_callback != NULL) { + Py_DECREF(self->psk_server_callback); + } + self->psk_server_callback = callback; + SSL_CTX_set_psk_server_callback(self->ctx, ssl_callback); + SSL_CTX_use_psk_identity_hint(self->ctx, identity_hint); + + Py_RETURN_NONE; +} + static PyGetSetDef context_getsetlist[] = { {"check_hostname", (getter) get_check_hostname, @@ -4668,6 +4868,8 @@ static struct PyMethodDef context_methods[] = { _SSL__SSLCONTEXT_CERT_STORE_STATS_METHODDEF _SSL__SSLCONTEXT_GET_CA_CERTS_METHODDEF _SSL__SSLCONTEXT_GET_CIPHERS_METHODDEF + _SSL__SSLCONTEXT_SET_PSK_CLIENT_CALLBACK_METHODDEF + _SSL__SSLCONTEXT_SET_PSK_SERVER_CALLBACK_METHODDEF {NULL, NULL} /* sentinel */ }; diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index 9f967ddc8e3061..c4c226b33934aa 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -1021,6 +1021,141 @@ _ssl__SSLContext_get_ca_certs(PySSLContext *self, PyObject *const *args, Py_ssiz return return_value; } +PyDoc_STRVAR(_ssl__SSLContext_set_psk_client_callback__doc__, +"set_psk_client_callback($self, /, callback)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT_SET_PSK_CLIENT_CALLBACK_METHODDEF \ + {"set_psk_client_callback", _PyCFunction_CAST(_ssl__SSLContext_set_psk_client_callback), METH_FASTCALL|METH_KEYWORDS, _ssl__SSLContext_set_psk_client_callback__doc__}, + +static PyObject * +_ssl__SSLContext_set_psk_client_callback_impl(PySSLContext *self, + PyObject *callback); + +static PyObject * +_ssl__SSLContext_set_psk_client_callback(PySSLContext *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(callback), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"callback", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "set_psk_client_callback", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *callback; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + callback = args[0]; + return_value = _ssl__SSLContext_set_psk_client_callback_impl(self, callback); + +exit: + return return_value; +} + +PyDoc_STRVAR(_ssl__SSLContext_set_psk_server_callback__doc__, +"set_psk_server_callback($self, /, callback, identity_hint=None)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT_SET_PSK_SERVER_CALLBACK_METHODDEF \ + {"set_psk_server_callback", _PyCFunction_CAST(_ssl__SSLContext_set_psk_server_callback), METH_FASTCALL|METH_KEYWORDS, _ssl__SSLContext_set_psk_server_callback__doc__}, + +static PyObject * +_ssl__SSLContext_set_psk_server_callback_impl(PySSLContext *self, + PyObject *callback, + const char *identity_hint); + +static PyObject * +_ssl__SSLContext_set_psk_server_callback(PySSLContext *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(callback), &_Py_ID(identity_hint), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"callback", "identity_hint", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "set_psk_server_callback", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + PyObject *callback; + const char *identity_hint = NULL; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); + if (!args) { + goto exit; + } + callback = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + if (args[1] == Py_None) { + identity_hint = NULL; + } + else if (PyUnicode_Check(args[1])) { + Py_ssize_t identity_hint_length; + identity_hint = PyUnicode_AsUTF8AndSize(args[1], &identity_hint_length); + if (identity_hint == NULL) { + goto exit; + } + if (strlen(identity_hint) != (size_t)identity_hint_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + } + else { + _PyArg_BadArgument("set_psk_server_callback", "argument 'identity_hint'", "str or None", args[1]); + goto exit; + } +skip_optional_pos: + return_value = _ssl__SSLContext_set_psk_server_callback_impl(self, callback, identity_hint); + +exit: + return return_value; +} + static PyObject * _ssl_MemoryBIO_impl(PyTypeObject *type); @@ -1542,4 +1677,4 @@ _ssl_enum_crls(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=4d9b81fa81f520f0 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=7840abea7709dfa8 input=a9049054013a1b77]*/ From cfddf7b858716c7dc1520e38c29d5cb6773a4ba2 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Mon, 3 Apr 2023 16:33:27 +1200 Subject: [PATCH 02/18] Fix TLS-PSK for TLS 1.3 SSL_CTX_set_session_id_context() is a server-side only operation. Using this on the client-side is causing authentication errors --- Doc/library/ssl.rst | 9 +++++++++ Lib/test/test_ssl.py | 32 ++++++++++++++++++++++++++++++++ Modules/_ssl.c | 8 ++++++-- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index bd1713890198b7..10b06a7d0679c6 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -2003,6 +2003,12 @@ to speed up repeated connections from the same clients. Setting ``callback`` to :const:`None` removes any existing callback. + .. note:: + When using TLS 1.3: + + - the ``hint`` parameter is always :const:`None`. + - client-identity must be a non-empty string. + Example usage:: context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -2041,6 +2047,9 @@ to speed up repeated connections from the same clients. The parameter ``identity_hint`` is an optional identity hint sent to the client. + .. note:: + When using TLS 1.3 the ``identity_hint`` parameter is not sent to the client. + Example usage:: context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index e200cda81f17f5..427ec45573dd82 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4259,6 +4259,38 @@ def server_callback(identity): with self.assertRaisesRegex(ssl.SSLError, 'Cannot add PSK client callback'): server_context.set_psk_client_callback(client_callback) + @requires_tls_version('TLSv1_3') + def test_psk_tls1_3(self): + psk = bytes.fromhex('deadbeef') + identity_hint = 'identity-hint' + client_identity = 'client-identity' + + def client_callback(hint): + # identity_hint is not sent to the client in TLS 1.3 + self.assertIsNone(hint) + return client_identity, psk + + def server_callback(identity): + self.assertEqual(identity, client_identity) + return psk + + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.check_hostname = False + client_context.verify_mode = ssl.CERT_NONE + client_context.minimum_version = ssl.TLSVersion.TLSv1_3 + client_context.set_ciphers('PSK') + client_context.set_psk_client_callback(client_callback) + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + server_context.minimum_version = ssl.TLSVersion.TLSv1_3 + server_context.set_ciphers('PSK') + server_context.set_psk_server_callback(server_callback, identity_hint) + + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + @unittest.skipUnless(has_tls_version('TLSv1_3'), "Test needs TLS 1.3") class TestPostHandshakeAuth(unittest.TestCase): diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 38d27c3473805d..baacdce0c804ac 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -3171,10 +3171,14 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version) usage for no cost at all. */ SSL_CTX_set_mode(self->ctx, SSL_MODE_RELEASE_BUFFERS); + /* Setting the session id context is a server-side only operation. + * It can cause unexpected behaviour on client-side connections. */ + if (proto_version == PY_SSL_VERSION_TLS_SERVER) { #define SID_CTX "Python" - SSL_CTX_set_session_id_context(self->ctx, (const unsigned char *) SID_CTX, - sizeof(SID_CTX)); + SSL_CTX_set_session_id_context(self->ctx, (const unsigned char *) SID_CTX, + sizeof(SID_CTX)); #undef SID_CTX + } params = SSL_CTX_get0_param(self->ctx); /* Improve trust chain building when cross-signed intermediate From 18302e3c46a10757c82f34c7c7c4310addc98f83 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Mon, 5 Jun 2023 09:47:47 +1200 Subject: [PATCH 03/18] Decode TLS-PSK identities as UTF-8 rather than ASCII RFC4279 states these are UTF-8. Add unit test using non-ASCII chars --- Lib/test/test_ssl.py | 11 +++++++++++ Modules/_ssl.c | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 19ceaa3c4d50f2..6dacaa5ab5ce04 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4259,6 +4259,17 @@ def server_callback(identity): with self.assertRaisesRegex(ssl.SSLError, 'Cannot add PSK client callback'): server_context.set_psk_client_callback(client_callback) + # test with UTF-8 identities + identity_hint = '身份暗示' # Translation: "Identity hint" + client_identity = '客户身份' # Translation: "Customer identity" + + client_context.set_psk_client_callback(client_callback) + server_context.set_psk_server_callback(server_callback, identity_hint) + server = ThreadedEchoServer(context=server_context) + with server: + with client_context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + @requires_tls_version('TLSv1_3') def test_psk_tls1_3(self): psk = bytes.fromhex('deadbeef') diff --git a/Modules/_ssl.c b/Modules/_ssl.c index ec1c98d4344e75..102be718022dd2 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4646,7 +4646,7 @@ static unsigned int psk_client_callback(SSL *s, } PyObject *hint_str = (hint != NULL) ? - PyUnicode_DecodeASCII(hint, strlen(hint), "strict") : + PyUnicode_DecodeUTF8(hint, strlen(hint), "strict") : Py_NewRef(Py_None); if (hint_str == NULL) { goto error; @@ -4743,7 +4743,7 @@ static unsigned int psk_server_callback(SSL *s, } PyObject *identity_str = (identity[0] != '\0') ? - PyUnicode_DecodeASCII(identity, strlen(identity), "strict") : + PyUnicode_DecodeUTF8(identity, strlen(identity), "strict") : Py_NewRef(Py_None); if (identity_str == NULL) { goto error; From 2efc8767cf6b531cdb5341f7ede4bf92e9625cb8 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Mon, 5 Jun 2023 09:49:12 +1200 Subject: [PATCH 04/18] Change TLS-PSK version added from 3.12 to 3.13 The PR has missed the 3.12 merge window --- Doc/library/ssl.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 9234c02b24c687..31d6ff979a4d37 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -2029,7 +2029,7 @@ to speed up repeated connections from the same clients. return 'ClientId_1', psk_table[hint] context.set_psk_client_callback(callback) - .. versionadded:: 3.12 + .. versionadded:: 3.13 .. method:: SSLContext.set_psk_server_callback(callback, identity_hint=None) @@ -2068,7 +2068,7 @@ to speed up repeated connections from the same clients. return psk_table[identity] context.set_psk_server_callback(callback, 'ServerId_1') - .. versionadded:: 3.12 + .. versionadded:: 3.13 .. index:: single: certificates From a56784ceb018721530b9427cbf2aa6904d3bedce Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Sun, 4 Jun 2023 20:39:53 -0700 Subject: [PATCH 05/18] ReSTify NEWS. --- .../next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst b/Misc/NEWS.d/next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst index d61f56df68dbe7..abb57dccd5a91a 100644 --- a/Misc/NEWS.d/next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst +++ b/Misc/NEWS.d/next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst @@ -1 +1 @@ -Added support for TLS-PSK (pre-shared key) to the ssl module +Added support for TLS-PSK (pre-shared key) mode to the :mod:`ssl` module. From 6f4100c3e466e49e12053240e31c70ee664860bc Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Sun, 16 Jul 2023 10:40:57 +1200 Subject: [PATCH 06/18] Set python exceptions raised during C callbacks as unraisable --- Modules/_ssl.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 81b32ce2cde375..32613f9737e0e6 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4653,12 +4653,13 @@ static unsigned int psk_client_callback(SSL *s, unsigned int max_psk_len) { PyGILState_STATE gstate = PyGILState_Ensure(); + PyObject *callback = NULL; PySSLSocket *ssl = SSL_get_app_data(s); if (ssl == NULL || ssl->ctx == NULL) { goto error; } - PyObject *callback = ssl->ctx->psk_client_callback; + callback = ssl->ctx->psk_client_callback; if (callback == NULL) { goto error; } @@ -4699,6 +4700,9 @@ static unsigned int psk_client_callback(SSL *s, return (unsigned int)psk_len_; error: + if (PyErr_Occurred()) { + PyErr_WriteUnraisable(callback); + } PyGILState_Release(gstate); return 0; } @@ -4750,12 +4754,13 @@ static unsigned int psk_server_callback(SSL *s, unsigned int max_psk_len) { PyGILState_STATE gstate = PyGILState_Ensure(); + PyObject *callback = NULL; PySSLSocket *ssl = SSL_get_app_data(s); if (ssl == NULL || ssl->ctx == NULL) { goto error; } - PyObject *callback = ssl->ctx->psk_server_callback; + callback = ssl->ctx->psk_server_callback; if (callback == NULL) { goto error; } @@ -4792,6 +4797,9 @@ static unsigned int psk_server_callback(SSL *s, return (unsigned int)psk_len_; error: + if (PyErr_Occurred()) { + PyErr_WriteUnraisable(callback); + } PyGILState_Release(gstate); return 0; } From e4a97ecbc89b89f01118f55e52dc9618b030dd66 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Sun, 16 Jul 2023 10:42:22 +1200 Subject: [PATCH 07/18] Add NULL check for defensive coding --- Modules/_ssl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 32613f9737e0e6..f9df7665efccd9 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4664,7 +4664,7 @@ static unsigned int psk_client_callback(SSL *s, goto error; } - PyObject *hint_str = (hint != NULL) ? + PyObject *hint_str = (hint != NULL && hint[0] != '\0') ? PyUnicode_DecodeUTF8(hint, strlen(hint), "strict") : Py_NewRef(Py_None); if (hint_str == NULL) { @@ -4765,7 +4765,7 @@ static unsigned int psk_server_callback(SSL *s, goto error; } - PyObject *identity_str = (identity[0] != '\0') ? + PyObject *identity_str = (identity != NULL && identity[0] != '\0') ? PyUnicode_DecodeUTF8(identity, strlen(identity), "strict") : Py_NewRef(Py_None); if (identity_str == NULL) { From 3d75982fdba8a112560974516d1a3bfacf2cc38a Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Sun, 16 Jul 2023 10:44:59 +1200 Subject: [PATCH 08/18] Get single value using PyBytes_AsStringAndSize There is no need to use PyArg_Parse in this simple case --- Modules/_ssl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index f9df7665efccd9..fb7832b3f3d45e 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4778,9 +4778,9 @@ static unsigned int psk_server_callback(SSL *s, goto error; } - const char *psk_; + char *psk_; Py_ssize_t psk_len_; - if (!PyArg_Parse(result, "y#", &psk_, &psk_len_)) { + if (PyBytes_AsStringAndSize(result, &psk_, &psk_len_) < 0) { Py_DECREF(result); goto error; } From 539ed1fc04da50d031f62c262c99a0117320e8af Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Sun, 16 Jul 2023 15:44:23 +1200 Subject: [PATCH 09/18] Do not raise a decode exception if remote side sends invalid UTF-8 during TLS-PSK negotiation In this case the remote side is breaking the standard (rfc4279). Just drop the connection --- Modules/_ssl.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index fb7832b3f3d45e..a25482fcb81d75 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4668,6 +4668,10 @@ static unsigned int psk_client_callback(SSL *s, PyUnicode_DecodeUTF8(hint, strlen(hint), "strict") : Py_NewRef(Py_None); if (hint_str == NULL) { + /* The remote side has sent an invalid UTF-8 string + * (breaking the standard), drop the connection without + * raising a decode exception. */ + PyErr_Clear(); goto error; } PyObject *result = PyObject_CallFunctionObjArgs(callback, hint_str, NULL); @@ -4769,6 +4773,10 @@ static unsigned int psk_server_callback(SSL *s, PyUnicode_DecodeUTF8(identity, strlen(identity), "strict") : Py_NewRef(Py_None); if (identity_str == NULL) { + /* The remote side has sent an invalid UTF-8 string + * (breaking the standard), drop the connection without + * raising a decode exception. */ + PyErr_Clear(); goto error; } PyObject *result = PyObject_CallFunctionObjArgs(callback, identity_str, NULL); From 4634fc300572e6941dd68b7a6b3311707758d821 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Sun, 16 Jul 2023 15:45:13 +1200 Subject: [PATCH 10/18] Free TLS-PSK callbacks when SSL context is deallocated --- Modules/_ssl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index a25482fcb81d75..dc6f88c018eebf 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -3232,6 +3232,8 @@ context_clear(PySSLContext *self) Py_CLEAR(self->set_sni_cb); Py_CLEAR(self->msg_cb); Py_CLEAR(self->keylog_filename); + Py_CLEAR(self->psk_client_callback); + Py_CLEAR(self->psk_server_callback); if (self->keylog_bio != NULL) { PySSL_BEGIN_ALLOW_THREADS BIO_free_all(self->keylog_bio); From d21c322bb4226f7c3683ecc53a8e91f3f2019215 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Sun, 16 Jul 2023 15:46:25 +1200 Subject: [PATCH 11/18] Use Py_XINCREF and Py_XDECREF for tidier NULL checking --- Modules/_ssl.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index dc6f88c018eebf..af8ba7f5f5bde1 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4741,13 +4741,12 @@ _ssl__SSLContext_set_psk_client_callback_impl(PySSLContext *self, PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; } - Py_INCREF(callback); ssl_callback = psk_client_callback; } - if (self->psk_client_callback != NULL) { - Py_DECREF(self->psk_client_callback); - } + Py_XDECREF(self->psk_client_callback); + Py_XINCREF(callback); + self->psk_client_callback = callback; SSL_CTX_set_psk_client_callback(self->ctx, ssl_callback); @@ -4845,13 +4844,12 @@ _ssl__SSLContext_set_psk_server_callback_impl(PySSLContext *self, PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; } - Py_INCREF(callback); ssl_callback = psk_server_callback; } - if (self->psk_server_callback != NULL) { - Py_DECREF(self->psk_server_callback); - } + Py_XDECREF(self->psk_server_callback); + Py_XINCREF(callback); + self->psk_server_callback = callback; SSL_CTX_set_psk_server_callback(self->ctx, ssl_callback); SSL_CTX_use_psk_identity_hint(self->ctx, identity_hint); From 03fac4ddc7c42231e5d0e6c79af8db2274a0ece8 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Sun, 16 Jul 2023 15:47:08 +1200 Subject: [PATCH 12/18] Check return value of SSL_CTX_use_psk_identity_hint --- Modules/_ssl.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index af8ba7f5f5bde1..d3e10436112726 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4847,12 +4847,16 @@ _ssl__SSLContext_set_psk_server_callback_impl(PySSLContext *self, ssl_callback = psk_server_callback; } + if (SSL_CTX_use_psk_identity_hint(self->ctx, identity_hint) != 1) { + PyErr_SetString(PyExc_ValueError, "failed to set identity hint"); + return NULL; + } + Py_XDECREF(self->psk_server_callback); Py_XINCREF(callback); self->psk_server_callback = callback; SSL_CTX_set_psk_server_callback(self->ctx, ssl_callback); - SSL_CTX_use_psk_identity_hint(self->ctx, identity_hint); Py_RETURN_NONE; } From 3db3921e29121fb62930469a608d1bc171831842 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Sun, 16 Jul 2023 16:05:48 +1200 Subject: [PATCH 13/18] Update TLS-PSK documentation Add length limits of identity and hint parameters. Add how to reject a connection --- Doc/library/ssl.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 31d6ff979a4d37..b40b459847eab8 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1998,8 +1998,10 @@ to speed up repeated connections from the same clients. The ``hint`` parameter is an optional identity hint sent by the server. The return value is a tuple in the form (client-identity, psk). Client-identity is an optional string which may be used by the server to - select a corresponding PSK for the client. PSK is a - :term:`bytes-like object` representing the pre-shared key. + select a corresponding PSK for the client. The string must be less than or + equal to ``256`` octets when UTF-8 encoded. PSK is a + :term:`bytes-like object` representing the pre-shared key. Return a zero + length PSK to reject the connection. Setting ``callback`` to :const:`None` removes any existing callback. @@ -2042,10 +2044,13 @@ to speed up repeated connections from the same clients. The ``identity`` parameter is an optional identity sent by the client which can be used to select a corresponding PSK. The return value is a :term:`bytes-like object` representing the pre-shared key. + Return a zero length PSK to reject the connection. Setting ``callback`` to :const:`None` removes any existing callback. - The parameter ``identity_hint`` is an optional identity hint sent to the client. + The parameter ``identity_hint`` is an optional identity hint string sent to + the client. The string must be less than or equal to ``256`` octets when + UTF-8 encoded. .. note:: When using TLS 1.3 the ``identity_hint`` parameter is not sent to the client. From a75f3a70d69268ab0275d147d7cf189659d77321 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Sun, 16 Jul 2023 16:09:46 +1200 Subject: [PATCH 14/18] Regenerate news entry to update the timestamp --- ...4.oMYuon.rst => 2023-07-16-16-08-06.gh-issue-63284.q2Qi9q.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Library/{2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst => 2023-07-16-16-08-06.gh-issue-63284.q2Qi9q.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst b/Misc/NEWS.d/next/Library/2023-07-16-16-08-06.gh-issue-63284.q2Qi9q.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2023-04-02-11-40-59.gh-issue-63284.oMYuon.rst rename to Misc/NEWS.d/next/Library/2023-07-16-16-08-06.gh-issue-63284.q2Qi9q.rst From 4c6897459860f89faaa1902781e6f2619f25ffdb Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Sun, 26 Nov 2023 19:31:43 +0000 Subject: [PATCH 15/18] Update example key strings in the doc. --- Doc/library/ssl.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 8840d108045a4e..fe7e315bf3c948 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -2039,12 +2039,12 @@ to speed up repeated connections from the same clients. context.set_ciphers('PSK') # A simple lambda: - psk = bytes.fromhex('deadbeef') + psk = bytes.fromhex('c0ffee') context.set_psk_client_callback(lambda hint: (None, psk)) # A table using the hint from the server: - psk_table = { 'ServerId_1': bytes.fromhex('deadbeef'), - 'ServerId_2': bytes.fromhex('cafebabe') + psk_table = { 'ServerId_1': bytes.fromhex('c0ffee'), + 'ServerId_2': bytes.fromhex('facade') } def callback(hint): return 'ClientId_1', psk_table[hint] @@ -2081,12 +2081,12 @@ to speed up repeated connections from the same clients. context.set_ciphers('PSK') # A simple lambda: - psk = bytes.fromhex('deadbeef') + psk = bytes.fromhex('c0ffee') context.set_psk_server_callback(lambda identity: psk) # A table using the identity of the client: - psk_table = { 'ClientId_1': bytes.fromhex('deadbeef'), - 'ClientId_2': bytes.fromhex('cafed00d') + psk_table = { 'ClientId_1': bytes.fromhex('c0ffee'), + 'ClientId_2': bytes.fromhex('facade') } def callback(identity): return psk_table[identity] From 0fb8a3e82b9c510c4795850cbc4ef27fe351c77a Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Mon, 27 Nov 2023 12:49:37 +1300 Subject: [PATCH 16/18] Update TLS-PSK documentation Demonstrate usage in a safer way that rejects unknown connection attempts --- Doc/library/ssl.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 8840d108045a4e..c9730a5069ec59 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -2047,7 +2047,7 @@ to speed up repeated connections from the same clients. 'ServerId_2': bytes.fromhex('cafebabe') } def callback(hint): - return 'ClientId_1', psk_table[hint] + return 'ClientId_1', psk_table.get(hint, b'') context.set_psk_client_callback(callback) .. versionadded:: 3.13 @@ -2089,7 +2089,7 @@ to speed up repeated connections from the same clients. 'ClientId_2': bytes.fromhex('cafed00d') } def callback(identity): - return psk_table[identity] + return psk_table.get(identity, b'') context.set_psk_server_callback(callback, 'ServerId_1') .. versionadded:: 3.13 From 77885893b0d9ece36b00fa49930c2743cdb81113 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Mon, 27 Nov 2023 12:50:14 +1300 Subject: [PATCH 17/18] Regenerate news entry to update the timestamp --- ...4.q2Qi9q.rst => 2023-11-27-12-41-23.gh-issue-63284.q2Qi9q.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Library/{2023-07-16-16-08-06.gh-issue-63284.q2Qi9q.rst => 2023-11-27-12-41-23.gh-issue-63284.q2Qi9q.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2023-07-16-16-08-06.gh-issue-63284.q2Qi9q.rst b/Misc/NEWS.d/next/Library/2023-11-27-12-41-23.gh-issue-63284.q2Qi9q.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2023-07-16-16-08-06.gh-issue-63284.q2Qi9q.rst rename to Misc/NEWS.d/next/Library/2023-11-27-12-41-23.gh-issue-63284.q2Qi9q.rst From 11735b997952530c922954ff07db688aad1dcbe6 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Mon, 27 Nov 2023 03:37:52 +0000 Subject: [PATCH 18/18] Code defensively around `z#` vs NULL and 0 length. --- Modules/_ssl.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 010f5f5196746b..707e7ad9543acb 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4707,7 +4707,7 @@ static unsigned int psk_client_callback(SSL *s, const char *psk_; const char *identity_; Py_ssize_t psk_len_; - Py_ssize_t identity_len_; + Py_ssize_t identity_len_ = 0; if (!PyArg_ParseTuple(result, "z#y#", &identity_, &identity_len_, &psk_, &psk_len_)) { Py_DECREF(result); goto error; @@ -4718,7 +4718,9 @@ static unsigned int psk_client_callback(SSL *s, goto error; } memcpy(psk, psk_, psk_len_); - memcpy(identity, identity_, identity_len_); + if (identity_ != NULL) { + memcpy(identity, identity_, identity_len_); + } identity[identity_len_] = 0; Py_DECREF(result);