From 16fa504b113818d67f77d8572006b371972addc8 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 18 Oct 2018 19:57:41 +0200 Subject: [PATCH 1/2] bpo-34271: Add ssl debugging helpers The ssl module now can dump key material to a keylog file and trace TLS protocol messages with a tracing callback. The default and stdlib contexts also support SSLKEYLOGFILE env var. --- Doc/library/ssl.rst | 116 ++++++++++ Lib/ssl.py | 137 ++++++++++- Lib/test/test_ssl.py | 156 ++++++++++++- .../2018-10-21-17-39-32.bpo-34271.P15VLM.rst | 3 + Modules/_ssl.c | 104 ++++++++- Modules/_ssl/debughelpers.c | 213 ++++++++++++++++++ setup.py | 12 +- 7 files changed, 723 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-10-21-17-39-32.bpo-34271.P15VLM.rst create mode 100644 Modules/_ssl/debughelpers.c diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 20f5724447164d..b0f730c6965613 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -139,6 +139,10 @@ purposes. *cadata* is given) or uses :meth:`SSLContext.load_default_certs` to load default CA certificates. + When :attr:`~SSLContext.keylog_filename` is supported and the environment + variable :envvar:`SSLKEYLOGFILE` is set, :func:`create_default_context` + enables key logging. + .. note:: The protocol, options, cipher and other settings may change to more restrictive values anytime without prior deprecation. The values @@ -172,6 +176,10 @@ purposes. 3DES was dropped from the default cipher string. + .. versionchanged:: 3.8 + + Support for key logging to :envvar:`SSLKEYLOGFILE` was added. + Exceptions ^^^^^^^^^^ @@ -1056,6 +1064,28 @@ Constants SSL 3.0 to TLS 1.3. +.. class:: TLSContentType + + :class:`enum.IntEnum` collection of SSL and TLS content types from + :rfc:`8443`, section B.1. + + .. versionadded:: 3.8 + +.. class:: TLSMessageType + + :class:`enum.IntEnum` collection of SSL and TLS message types from + :rfc:`8443`, section B.3. + + .. versionadded:: 3.8 + +.. class:: TLSAlertType + + :class:`enum.IntEnum` collection of SSL and TLS alert types from + :rfc:`8443`, section B.2. + + .. versionadded:: 3.8 + + SSL Sockets ----------- @@ -1901,6 +1931,20 @@ to speed up repeated connections from the same clients. This features requires OpenSSL 0.9.8f or newer. +.. attribute:: SSLContext.keylog_filename + + Write TLS keys to a keylog file, whenever key material is generated or + received. The keylog file is designed for debugging purposes only. The + file format is specified by NSS and used by many traffic analyzers such + as Wireshark. The log file is opened in append-only mode. Writes a + synchronized between threads, but not between processes. + + .. versionadded:: 3.8 + + .. note:: + + This features requires OpenSSL 1.1.1 or newer. + .. attribute:: SSLContext.maximum_version A :class:`TLSVersion` enum member representing the highest supported @@ -1936,6 +1980,78 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.7 +.. attribute:: SSLContext.msg_callback + + The message callback provides a debugging hook to analyze TLS connections. + The callback is called for any TLS protocol message (header, handshake, + alert, and more), but not for application data. Due to technical + limitations, the callback can't be used to filter traffic or to abort a + connection. Any exception raised in the callback is delayed until the + handshake, read, or write operation has been performed. + + Example:: + + def msg_cb(conn, direction, version, content_type, msg_type, data): + print(f"{direction:5} {version._name_:7} {content_type._name_:18} " + f"{msg_type._name_:20} {len(data):4}") + + ctx = ssl.create_default_context() + ctx.msg_callback = msg_cb + host = 'tls13.crypto.mozilla.org' + with ctx.wrap_socket(socket.socket(), server_hostname=host) as s: + s.connect((host, 443)) + s.sendall(b'HEAD / HTTP/1.1\r\n') + s.sendall(b'Host: %s\r\n\r\n' % host) + s.recv(4096) + s.unwrap() + + Output:: + + write TLSv1 HEADER HANDSHAKE 5 + write TLSv1_3 HANDSHAKE CLIENT_HELLO 512 + read TLSv1_2 HEADER HANDSHAKE 5 + read TLSv1_3 HANDSHAKE SERVER_HELLO 122 + read TLSv1_2 HEADER CHANGE_CIPHER_SPEC 5 + read TLSv1_2 HEADER APPLICATION_DATA 5 + read TLSv1_3 INNER_CONTENT_TYPE CERTIFICATE_STATUS 1 + read TLSv1_3 HANDSHAKE ENCRYPTED_EXTENSIONS 6 + read TLSv1_3 HANDSHAKE CERTIFICATE 3226 + read TLSv1_3 HANDSHAKE CERTIFICATE_VERIFY 79 + read TLSv1_3 HANDSHAKE FINISHED 52 + write TLSv1_2 HEADER CHANGE_CIPHER_SPEC 5 + write TLSv1_3 CHANGE_CIPHER_SPEC CHANGE_CIPHER_SPEC 1 + write TLSv1_2 HEADER APPLICATION_DATA 5 + write TLSv1_3 INNER_CONTENT_TYPE CERTIFICATE_STATUS 1 + write TLSv1_3 HANDSHAKE FINISHED 52 + write TLSv1_2 HEADER APPLICATION_DATA 5 + write TLSv1_3 INNER_CONTENT_TYPE SUPPLEMENTAL_DATA 1 + write TLSv1_2 HEADER APPLICATION_DATA 5 + write TLSv1_3 INNER_CONTENT_TYPE SUPPLEMENTAL_DATA 1 + read TLSv1_2 HEADER APPLICATION_DATA 5 + read TLSv1_3 INNER_CONTENT_TYPE CERTIFICATE_STATUS 1 + read TLSv1_3 HANDSHAKE NEWSESSION_TICKET 238 + read TLSv1_3 HANDSHAKE NEWSESSION_TICKET 238 + + + conn + :class:`SSLSocket` or :class:`SSLObject` instance + direction + ``read`` or ``write`` + version + :class:`TLSVersion` enum member or int for unknown version. For a + frame header, it's the header version. + content_type + :class:`TLSContentType` enum member or int for unsupported content type. + msg_type + Either a :class:`TLSContentType` enum number for a header message, + a :class:`TLSAlertType` enum member for an alert message, + a :class:`TLSMessageType` enum member for other messages, or int for + unsupported message types. + data + Raw, decrypted message content as bytes + + .. versionadded:: 3.8 + .. attribute:: SSLContext.options An integer representing the set of SSL options enabled on this context. diff --git a/Lib/ssl.py b/Lib/ssl.py index 793ed496c77af4..4f3b0befbcc22c 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -165,6 +165,90 @@ class TLSVersion(_IntEnum): MAXIMUM_SUPPORTED = _ssl.PROTO_MAXIMUM_SUPPORTED +class TLSContentType(_IntEnum): + """Content types (record layer) + + See RFC 8446, section B.1 + """ + CHANGE_CIPHER_SPEC = 20 + ALERT = 21 + HANDSHAKE = 22 + APPLICATION_DATA = 23 + # pseudo content types + HEADER = 0x100 + INNER_CONTENT_TYPE = 0x101 + + +class TLSAlertType(_IntEnum): + """Alert types for TLSContentType.ALERT messages + + See RFC 8466, section B.2 + """ + CLOSE_NOTIFY = 0 + UNEXPECTED_MESSAGE = 10 + BAD_RECORD_MAC = 20 + DECRYPTION_FAILED = 21 + RECORD_OVERFLOW = 22 + DECOMPRESSION_FAILURE = 30 + HANDSHAKE_FAILURE = 40 + NO_CERTIFICATE = 41 + BAD_CERTIFICATE = 42 + UNSUPPORTED_CERTIFICATE = 43 + CERTIFICATE_REVOKED = 44 + CERTIFICATE_EXPIRED = 45 + CERTIFICATE_UNKNOWN = 46 + ILLEGAL_PARAMETER = 47 + UNKNOWN_CA = 48 + ACCESS_DENIED = 49 + DECODE_ERROR = 50 + DECRYPT_ERROR = 51 + EXPORT_RESTRICTION = 60 + PROTOCOL_VERSION = 70 + INSUFFICIENT_SECURITY = 71 + INTERNAL_ERROR = 80 + INAPPROPRIATE_FALLBACK = 86 + USER_CANCELED = 90 + NO_RENEGOTIATION = 100 + MISSING_EXTENSION = 109 + UNSUPPORTED_EXTENSION = 110 + CERTIFICATE_UNOBTAINABLE = 111 + UNRECOGNIZED_NAME = 112 + BAD_CERTIFICATE_STATUS_RESPONSE = 113 + BAD_CERTIFICATE_HASH_VALUE = 114 + UNKNOWN_PSK_IDENTITY = 115 + CERTIFICATE_REQUIRED = 116 + NO_APPLICATION_PROTOCOL = 120 + + +class TLSMessageType(_IntEnum): + """Message types (handshake protocol) + + See RFC 8446, section B.3 + """ + HELLO_REQUEST = 0 + CLIENT_HELLO = 1 + SERVER_HELLO = 2 + HELLO_VERIFY_REQUEST = 3 + NEWSESSION_TICKET = 4 + END_OF_EARLY_DATA = 5 + HELLO_RETRY_REQUEST = 6 + ENCRYPTED_EXTENSIONS = 8 + CERTIFICATE = 11 + SERVER_KEY_EXCHANGE = 12 + CERTIFICATE_REQUEST = 13 + SERVER_DONE = 14 + CERTIFICATE_VERIFY = 15 + CLIENT_KEY_EXCHANGE = 16 + FINISHED = 20 + CERTIFICATE_URL = 21 + CERTIFICATE_STATUS = 22 + SUPPLEMENTAL_DATA = 23 + KEY_UPDATE = 24 + NEXT_PROTO = 67 + MESSAGE_HASH = 254 + CHANGE_CIPHER_SPEC = 0x0101 + + if sys.platform == "win32": from _ssl import enum_certificates, enum_crls @@ -523,6 +607,48 @@ def hostname_checks_common_name(self, value): def hostname_checks_common_name(self): return True + @property + def msg_callback(self): + return super().msg_callback.user_function + + @msg_callback.setter + def msg_callback(self, callback): + if callback is None: + super(SSLContext, SSLContext).msg_callback.__set__(self, None) + return + + if not hasattr(callback, '__call__'): + raise TypeError(f"{callback} is not callable.") + + def inner(conn, direction, version, content_type, msg_type, data): + try: + version = TLSVersion(version) + except TypeError: + pass + + try: + content_type = TLSContentType(content_type) + except TypeError: + pass + + if content_type == TLSContentType.HEADER: + msg_enum = TLSContentType + elif content_type == TLSContentType.ALERT: + msg_enum = TLSAlertType + else: + msg_enum = TLSMessageType + try: + msg_type = msg_enum(msg_type) + except TypeError: + pass + + return callback(conn, direction, version, + content_type, msg_type, data) + + inner.user_function = callback + + super(SSLContext, SSLContext).msg_callback.__set__(self, inner) + @property def protocol(self): return _SSLMethod(super().protocol) @@ -576,6 +702,11 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, # CERT_OPTIONAL or CERT_REQUIRED. Let's try to load default system # root CA certificates for the given purpose. This may fail silently. context.load_default_certs(purpose) + # OpenSSL 1.1.1 keylog file + if hasattr(context, 'keylog_filename'): + keylogfile = os.environ.get('SSLKEYLOGFILE') + if keylogfile and not sys.flags.ignore_environment: + context.keylog_filename = keylogfile return context def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=CERT_NONE, @@ -617,7 +748,11 @@ def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=CERT_NONE, # CERT_OPTIONAL or CERT_REQUIRED. Let's try to load default system # root CA certificates for the given purpose. This may fail silently. context.load_default_certs(purpose) - + # OpenSSL 1.1.1 keylog file + if hasattr(context, 'keylog_filename'): + keylogfile = os.environ.get('SSLKEYLOGFILE') + if keylogfile and not sys.flags.ignore_environment: + context.keylog_filename = keylogfile return context # Used by http.client if no context is explicitly passed. diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index d48d6e5569fc3e..d053575ef6cf0b 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -2,6 +2,7 @@ import sys import unittest +import unittest.mock from test import support import socket import select @@ -25,6 +26,7 @@ ssl = support.import_module("ssl") +from ssl import TLSVersion, TLSContentType, TLSMessageType, TLSAlertType PROTOCOLS = sorted(ssl._PROTOCOL_NAMES) HOST = support.HOST @@ -4405,6 +4407,158 @@ def test_pha_not_tls13(self): self.assertIn(b'WRONG_SSL_VERSION', s.recv(1024)) +HAS_KEYLOG = hasattr(ssl.SSLContext, 'keylog_filename') +requires_keylog = unittest.skipUnless( + HAS_KEYLOG, 'test requires OpenSSL 1.1.1 with keylog callback') + +class TestSSLDebug(unittest.TestCase): + + def keylog_lines(self, fname=support.TESTFN): + with open(fname) as f: + return len(list(f)) + + @requires_keylog + def test_keylog_defaults(self): + self.addCleanup(support.unlink, support.TESTFN) + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.keylog_filename, None) + + self.assertFalse(os.path.isfile(support.TESTFN)) + ctx.keylog_filename = support.TESTFN + self.assertEqual(ctx.keylog_filename, support.TESTFN) + self.assertTrue(os.path.isfile(support.TESTFN)) + self.assertEqual(self.keylog_lines(), 1) + + ctx.keylog_filename = None + self.assertEqual(ctx.keylog_filename, None) + + with self.assertRaises((IsADirectoryError, PermissionError)): + # Windows raises PermissionError + ctx.keylog_filename = os.path.dirname( + os.path.abspath(support.TESTFN)) + + with self.assertRaises(TypeError): + ctx.keylog_filename = 1 + + @requires_keylog + def test_keylog_filename(self): + self.addCleanup(support.unlink, support.TESTFN) + client_context, server_context, hostname = testing_context() + + client_context.keylog_filename = support.TESTFN + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + # header, 5 lines for TLS 1.3 + self.assertEqual(self.keylog_lines(), 6) + + client_context.keylog_filename = None + server_context.keylog_filename = support.TESTFN + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + self.assertGreaterEqual(self.keylog_lines(), 11) + + client_context.keylog_filename = support.TESTFN + server_context.keylog_filename = support.TESTFN + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + self.assertGreaterEqual(self.keylog_lines(), 21) + + client_context.keylog_filename = None + server_context.keylog_filename = None + + @requires_keylog + @unittest.skipIf(sys.flags.ignore_environment, + "test is not compatible with ignore_environment") + def test_keylog_env(self): + self.addCleanup(support.unlink, support.TESTFN) + with unittest.mock.patch.dict(os.environ): + os.environ['SSLKEYLOGFILE'] = support.TESTFN + self.assertEqual(os.environ['SSLKEYLOGFILE'], support.TESTFN) + + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.keylog_filename, None) + + ctx = ssl.create_default_context() + self.assertEqual(ctx.keylog_filename, support.TESTFN) + + ctx = ssl._create_stdlib_context() + self.assertEqual(ctx.keylog_filename, support.TESTFN) + + def test_msg_callback_tls12(self): + client_context, server_context, hostname = testing_context() + client_context.options |= ssl.OP_NO_TLSv1_3 + + msg = [] + + def msg_cb(conn, direction, version, content_type, msg_type, data): + self.assertIsInstance(conn, ssl.SSLSocket) + self.assertIsInstance(data, bytes) + self.assertIn(direction, {'read', 'write'}) + msg.append((direction, version, content_type, msg_type)) + + client_context.msg_callback = msg_cb + + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + + self.assertEqual(msg, [ + ("write", TLSVersion.TLSv1, TLSContentType.HEADER, + TLSMessageType.CERTIFICATE_STATUS), + ("write", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, + TLSMessageType.CLIENT_HELLO), + ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, + TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, + TLSMessageType.SERVER_HELLO), + ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, + TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, + TLSMessageType.CERTIFICATE), + ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, + TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, + TLSMessageType.SERVER_KEY_EXCHANGE), + ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, + TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, + TLSMessageType.SERVER_DONE), + ("write", TLSVersion.TLSv1_2, TLSContentType.HEADER, + TLSMessageType.CERTIFICATE_STATUS), + ("write", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, + TLSMessageType.CLIENT_KEY_EXCHANGE), + ("write", TLSVersion.TLSv1_2, TLSContentType.HEADER, + TLSMessageType.FINISHED), + ("write", TLSVersion.TLSv1_2, TLSContentType.CHANGE_CIPHER_SPEC, + TLSMessageType.CHANGE_CIPHER_SPEC), + ("write", TLSVersion.TLSv1_2, TLSContentType.HEADER, + TLSMessageType.CERTIFICATE_STATUS), + ("write", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, + TLSMessageType.FINISHED), + ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, + TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, + TLSMessageType.NEWSESSION_TICKET), + ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, + TLSMessageType.FINISHED), + ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, + TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, + TLSMessageType.FINISHED), + ]) + + def test_main(verbose=False): if support.verbose: import warnings @@ -4440,7 +4594,7 @@ def test_main(verbose=False): tests = [ ContextTests, BasicSocketTests, SSLErrorTests, MemoryBIOTests, SSLObjectTests, SimpleBackgroundTests, ThreadedTests, - TestPostHandshakeAuth + TestPostHandshakeAuth, TestSSLDebug ] if support.is_resource_enabled('network'): diff --git a/Misc/NEWS.d/next/Library/2018-10-21-17-39-32.bpo-34271.P15VLM.rst b/Misc/NEWS.d/next/Library/2018-10-21-17-39-32.bpo-34271.P15VLM.rst new file mode 100644 index 00000000000000..344388f7f2287f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-10-21-17-39-32.bpo-34271.P15VLM.rst @@ -0,0 +1,3 @@ +Add debugging helpers to ssl module. It's now possible to dump key material +and to trace TLS protocol. The default and stdlib contexts also support +SSLKEYLOGFILE env var. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 4fb7dca9bb04ae..a8af42343c6770 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -185,6 +185,10 @@ static void _PySSLFixErrno(void) { # define HAVE_NPN 0 #endif +#if (OPENSSL_VERSION_NUMBER >= 0x10101000L) && !defined(LIBRESSL_VERSION_NUMBER) +#define HAVE_OPENSSL_KEYLOG 1 +#endif + #ifndef INVALID_SOCKET /* MS defines this */ #define INVALID_SOCKET (-1) #endif @@ -423,6 +427,11 @@ typedef struct { int protocol; #ifdef TLS1_3_VERSION int post_handshake_auth; +#endif + PyObject *msg_cb; +#ifdef HAVE_OPENSSL_KEYLOG + PyObject *keylog_filename; + BIO *keylog_bio; #endif } PySSLContext; @@ -444,6 +453,13 @@ typedef struct { PyObject *owner; /* Python level "owner" passed to servername callback */ PyObject *server_hostname; _PySSLError err; /* last seen error from various sources */ + /* Some SSL callbacks don't have error reporting. Callback wrappers + * store exception information on the socket. The handshake, read, write, + * and shutdown methods check for chained exceptions. + */ + PyObject *exc_type; + PyObject *exc_value; + PyObject *exc_tb; } PySSLSocket; typedef struct { @@ -517,6 +533,8 @@ typedef enum { #define GET_SOCKET_TIMEOUT(sock) \ ((sock != NULL) ? (sock)->sock_timeout : 0) +#include "_ssl/debughelpers.c" + /* * SSL errors. */ @@ -703,6 +721,18 @@ fill_and_set_sslerror(PySSLSocket *sslsock, PyObject *type, int ssl_errno, Py_XDECREF(verify_obj); } +static int +PySSL_ChainExceptions(PySSLSocket *sslsock) { + if (sslsock->exc_type == NULL) + return 0; + + _PyErr_ChainExceptions(sslsock->exc_type, sslsock->exc_value, sslsock->exc_tb); + sslsock->exc_type = NULL; + sslsock->exc_value = NULL; + sslsock->exc_tb = NULL; + return -1; +} + static PyObject * PySSL_SetError(PySSLSocket *sslsock, int ret, const char *filename, int lineno) { @@ -796,6 +826,7 @@ PySSL_SetError(PySSLSocket *sslsock, int ret, const char *filename, int lineno) } fill_and_set_sslerror(sslsock, type, p, errstr, lineno, e); ERR_clear_error(); + PySSL_ChainExceptions(sslsock); return NULL; } @@ -903,6 +934,9 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock, self->owner = NULL; self->server_hostname = NULL; self->err = err; + self->exc_type = NULL; + self->exc_value = NULL; + self->exc_tb = NULL; /* Make sure the SSL error state is initialized */ ERR_clear_error(); @@ -1052,11 +1086,12 @@ _ssl__SSLSocket_do_handshake_impl(PySSLSocket *self) Py_XDECREF(sock); if (ret < 1) return PySSL_SetError(self, ret, __FILE__, __LINE__); - + if (PySSL_ChainExceptions(self) < 0) + return NULL; Py_RETURN_NONE; - error: Py_XDECREF(sock); + PySSL_ChainExceptions(self); return NULL; } @@ -2151,8 +2186,26 @@ PyDoc_STRVAR(PySSL_get_owner_doc, "The Python-level owner of this object.\ Passed as \"self\" in servername callback."); +static int +PySSL_traverse(PySSLSocket *self, visitproc visit, void *arg) +{ + Py_VISIT(self->exc_type); + Py_VISIT(self->exc_value); + Py_VISIT(self->exc_tb); + return 0; +} + +static int +PySSL_clear(PySSLSocket *self) +{ + Py_CLEAR(self->exc_type); + Py_CLEAR(self->exc_value); + Py_CLEAR(self->exc_tb); + return 0; +} -static void PySSL_dealloc(PySSLSocket *self) +static void +PySSL_dealloc(PySSLSocket *self) { if (self->ssl) SSL_free(self->ssl); @@ -2333,13 +2386,14 @@ _ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b) err.ssl == SSL_ERROR_WANT_WRITE); Py_XDECREF(sock); - if (len > 0) - return PyLong_FromLong(len); - else + if (len <= 0) return PySSL_SetError(self, len, __FILE__, __LINE__); - + if (PySSL_ChainExceptions(self) < 0) + return NULL; + return PyLong_FromLong(len); error: Py_XDECREF(sock); + PySSL_ChainExceptions(self); return NULL; } @@ -2486,6 +2540,8 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, int len, int group_right_1, PySSL_SetError(self, count, __FILE__, __LINE__); goto error; } + if (self->exc_type != NULL) + goto error; done: Py_XDECREF(sock); @@ -2498,6 +2554,7 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, int len, int group_right_1, } error: + PySSL_ChainExceptions(self); Py_XDECREF(sock); if (!group_right_1) Py_XDECREF(dest); @@ -2601,11 +2658,13 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self) /* Retain the SSL error code */ break; } - if (ret < 0) { Py_XDECREF(sock); - return PySSL_SetError(self, ret, __FILE__, __LINE__); + PySSL_SetError(self, ret, __FILE__, __LINE__); + return NULL; } + if (self->exc_type != NULL) + goto error; if (sock) /* It's already INCREF'ed */ return (PyObject *) sock; @@ -2614,6 +2673,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self) error: Py_XDECREF(sock); + PySSL_ChainExceptions(self); return NULL; } @@ -2889,8 +2949,8 @@ static PyTypeObject PySSLSocket_Type = { 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ + (traverseproc) PySSL_traverse, /*tp_traverse*/ + (inquiry) PySSL_clear, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ @@ -3002,6 +3062,11 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version) self->ctx = ctx; self->hostflags = X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS; self->protocol = proto_version; + self->msg_cb = NULL; +#ifdef HAVE_OPENSSL_KEYLOG + self->keylog_filename = NULL; + self->keylog_bio = NULL; +#endif #if HAVE_NPN self->npn_protocols = NULL; #endif @@ -3127,6 +3192,7 @@ context_traverse(PySSLContext *self, visitproc visit, void *arg) #ifndef OPENSSL_NO_TLSEXT Py_VISIT(self->set_sni_cb); #endif + Py_VISIT(self->msg_cb); return 0; } @@ -3135,6 +3201,16 @@ context_clear(PySSLContext *self) { #ifndef OPENSSL_NO_TLSEXT Py_CLEAR(self->set_sni_cb); +#endif + Py_CLEAR(self->msg_cb); +#ifdef HAVE_OPENSSL_KEYLOG + Py_CLEAR(self->keylog_filename); + if (self->keylog_bio != NULL) { + PySSL_BEGIN_ALLOW_THREADS + BIO_free_all(self->keylog_bio); + PySSL_END_ALLOW_THREADS + self->keylog_bio = NULL; + } #endif return 0; } @@ -4570,6 +4646,12 @@ static PyGetSetDef context_getsetlist[] = { {"maximum_version", (getter) get_maximum_version, (setter) set_maximum_version, NULL}, #endif +#ifdef HAVE_OPENSSL_KEYLOG + {"keylog_filename", (getter) _PySSLContext_get_keylog_filename, + (setter) _PySSLContext_set_keylog_filename, NULL}, +#endif + {"msg_callback", (getter) _PySSLContext_get_msg_callback, + (setter) _PySSLContext_set_msg_callback, NULL}, {"sni_callback", (getter) get_sni_callback, (setter) set_sni_callback, PySSLContext_sni_callback_doc}, {"options", (getter) get_options, diff --git a/Modules/_ssl/debughelpers.c b/Modules/_ssl/debughelpers.c new file mode 100644 index 00000000000000..53b96674932805 --- /dev/null +++ b/Modules/_ssl/debughelpers.c @@ -0,0 +1,213 @@ +/* Debug helpers */ + +static void +_PySSL_msg_callback(int write_p, int version, int content_type, + const void *buf, size_t len, SSL *ssl, void *arg) +{ + const char *cbuf = (const char *)buf; + PyGILState_STATE threadstate; + PyObject *res = NULL; + PySSLSocket *ssl_obj = NULL; /* ssl._SSLSocket, borrowed ref */ + PyObject *ssl_socket = NULL; /* ssl.SSLSocket or ssl.SSLObject */ + int msg_type; + + threadstate = PyGILState_Ensure(); + + ssl_obj = (PySSLSocket *)SSL_get_app_data(ssl); + assert(PySSLSocket_Check(ssl_obj)); + if (ssl_obj->ctx->msg_cb == NULL) { + return; + } + + if (ssl_obj->owner) + ssl_socket = PyWeakref_GetObject(ssl_obj->owner); + else if (ssl_obj->Socket) + ssl_socket = PyWeakref_GetObject(ssl_obj->Socket); + else + ssl_socket = (PyObject *)ssl_obj; + Py_INCREF(ssl_socket); + + /* assume that OpenSSL verifies all payload and buf len is of sufficient + length */ + switch(content_type) { + case SSL3_RT_CHANGE_CIPHER_SPEC: + msg_type = SSL3_MT_CHANGE_CIPHER_SPEC; + break; + case SSL3_RT_ALERT: + /* byte 0: level */ + /* byte 1: alert type */ + msg_type = (int)cbuf[1]; + break; + case SSL3_RT_HANDSHAKE: + msg_type = (int)cbuf[0]; + break; + case SSL3_RT_HEADER: + /* frame header encodes version in bytes 1..2 */ + version = cbuf[1] << 8 | cbuf[2]; + msg_type = (int)cbuf[0]; + break; +#ifdef SSL3_RT_INNER_CONTENT_TYPE + case SSL3_RT_INNER_CONTENT_TYPE: + msg_type = (int)cbuf[0]; + break; +#endif + default: + /* never SSL3_RT_APPLICATION_DATA */ + msg_type = -1; + break; + } + + res = PyObject_CallFunction( + ssl_obj->ctx->msg_cb, "Osiiiy#", + ssl_socket, write_p ? "write" : "read", + version, content_type, msg_type, + buf, len + ); + if (res == NULL) { + PyErr_Fetch(&ssl_obj->exc_type, &ssl_obj->exc_value, &ssl_obj->exc_tb); + } else { + Py_DECREF(res); + } + Py_XDECREF(ssl_socket); + + PyGILState_Release(threadstate); +} + + +static PyObject * +_PySSLContext_get_msg_callback(PySSLContext *self, void *c) { + if (self->msg_cb != NULL) { + Py_INCREF(self->msg_cb); + return self->msg_cb; + } else { + Py_RETURN_NONE; + } +} + +static int +_PySSLContext_set_msg_callback(PySSLContext *self, PyObject *arg, void *c) { + Py_CLEAR(self->msg_cb); + if (arg == Py_None) { + SSL_CTX_set_msg_callback(self->ctx, NULL); + } + else { + if (!PyCallable_Check(arg)) { + SSL_CTX_set_msg_callback(self->ctx, NULL); + PyErr_SetString(PyExc_TypeError, + "not a callable object"); + return -1; + } + Py_INCREF(arg); + self->msg_cb = arg; + SSL_CTX_set_msg_callback(self->ctx, _PySSL_msg_callback); + } + return 0; +} + +#ifdef HAVE_OPENSSL_KEYLOG + +static void +_PySSL_keylog_callback(const SSL *ssl, const char *line) +{ + PyGILState_STATE threadstate; + PySSLSocket *ssl_obj = NULL; /* ssl._SSLSocket, borrowed ref */ + int res, e; + static PyThread_type_lock *lock = NULL; + + threadstate = PyGILState_Ensure(); + + /* Allocate a static lock to synchronize writes to keylog file. + * The lock is neither released on exit nor on fork(). The lock is + * also shared between all SSLContexts although contexts may write to + * their own files. IMHO that's good enough for a non-performance + * critical debug helper. + */ + if (lock == NULL) { + lock = PyThread_allocate_lock(); + if (lock == NULL) { + PyErr_SetString(PyExc_MemoryError, "Unable to allocate lock"); + PyErr_Fetch(&ssl_obj->exc_type, &ssl_obj->exc_value, + &ssl_obj->exc_tb); + return; + } + } + + ssl_obj = (PySSLSocket *)SSL_get_app_data(ssl); + assert(PySSLSocket_Check(ssl_obj)); + if (ssl_obj->ctx->keylog_bio == NULL) { + return; + } + + PySSL_BEGIN_ALLOW_THREADS + PyThread_acquire_lock(lock, 1); + res = BIO_printf(ssl_obj->ctx->keylog_bio, "%s\n", line); + e = errno; + (void)BIO_flush(ssl_obj->ctx->keylog_bio); + PyThread_release_lock(lock); + PySSL_END_ALLOW_THREADS + + if (res == -1) { + errno = e; + PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, + ssl_obj->ctx->keylog_filename); + PyErr_Fetch(&ssl_obj->exc_type, &ssl_obj->exc_value, &ssl_obj->exc_tb); + } + PyGILState_Release(threadstate); +} + +static PyObject * +_PySSLContext_get_keylog_filename(PySSLContext *self, void *c) { + if (self->keylog_filename != NULL) { + Py_INCREF(self->keylog_filename); + return self->keylog_filename; + } else { + Py_RETURN_NONE; + } +} + +static int +_PySSLContext_set_keylog_filename(PySSLContext *self, PyObject *arg, void *c) { + FILE *fp; + /* Reset variables and callback first */ + SSL_CTX_set_keylog_callback(self->ctx, NULL); + Py_CLEAR(self->keylog_filename); + if (self->keylog_bio != NULL) { + BIO *bio = self->keylog_bio; + self->keylog_bio = NULL; + PySSL_BEGIN_ALLOW_THREADS + BIO_free_all(bio); + PySSL_END_ALLOW_THREADS + } + + if (arg == Py_None) { + /* None disables the callback */ + return 0; + } + + /* _Py_fopen_obj() also checks that arg is of proper type. */ + fp = _Py_fopen_obj(arg, "a" PY_STDIOTEXTMODE); + if (fp == NULL) + return -1; + + self->keylog_bio = BIO_new_fp(fp, BIO_CLOSE | BIO_FP_TEXT); + if (self->keylog_bio == NULL) { + PyErr_SetString(PySSLErrorObject, + "Can't malloc memory for keylog file"); + return -1; + } + Py_INCREF(arg); + self->keylog_filename = arg; + + /* Write a header for seekable, empty files (this excludes pipes). */ + PySSL_BEGIN_ALLOW_THREADS + if (BIO_tell(self->keylog_bio) == 0) { + BIO_puts(self->keylog_bio, + "# TLS secrets log file, generated by OpenSSL / Python\n"); + (void)BIO_flush(self->keylog_bio); + } + PySSL_END_ALLOW_THREADS + SSL_CTX_set_keylog_callback(self->ctx, _PySSL_keylog_callback); + return 0; +} + +#endif \ No newline at end of file diff --git a/setup.py b/setup.py index 96a49b4e353c72..7852c2dfa27e08 100644 --- a/setup.py +++ b/setup.py @@ -2178,11 +2178,13 @@ def split_var(name, sep): ssl_incs.extend(krb5_h) if config_vars.get("HAVE_X509_VERIFY_PARAM_SET1_HOST"): - self.add(Extension('_ssl', ['_ssl.c'], - include_dirs=openssl_includes, - library_dirs=openssl_libdirs, - libraries=openssl_libs, - depends=['socketmodule.h'])) + self.add(Extension( + '_ssl', ['_ssl.c'], + include_dirs=openssl_includes, + library_dirs=openssl_libdirs, + libraries=openssl_libs, + depends=['socketmodule.h', '_ssl/debughelpers.c']) + ) else: self.missing.append('_ssl') From 9bbf7a463411b15bbddfe8b21b4f0e06f60ed93a Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 30 May 2019 23:50:36 +0200 Subject: [PATCH 2/2] Make msg_callback a private debug function The msg_callback and related enums are now private members. The feature is designed for internal debugging and not for end users. Signed-off-by: Christian Heimes --- Doc/library/ssl.rst | 95 +--------------------------------------- Lib/ssl.py | 65 +++++++++++++++++++++------- Lib/test/test_ssl.py | 100 ++++++++++++++++++++++++------------------- Modules/_ssl.c | 4 +- 4 files changed, 109 insertions(+), 155 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index b0f730c6965613..be09f38f7dfa0b 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1064,27 +1064,6 @@ Constants SSL 3.0 to TLS 1.3. -.. class:: TLSContentType - - :class:`enum.IntEnum` collection of SSL and TLS content types from - :rfc:`8443`, section B.1. - - .. versionadded:: 3.8 - -.. class:: TLSMessageType - - :class:`enum.IntEnum` collection of SSL and TLS message types from - :rfc:`8443`, section B.3. - - .. versionadded:: 3.8 - -.. class:: TLSAlertType - - :class:`enum.IntEnum` collection of SSL and TLS alert types from - :rfc:`8443`, section B.2. - - .. versionadded:: 3.8 - SSL Sockets ----------- @@ -1936,7 +1915,7 @@ to speed up repeated connections from the same clients. Write TLS keys to a keylog file, whenever key material is generated or received. The keylog file is designed for debugging purposes only. The file format is specified by NSS and used by many traffic analyzers such - as Wireshark. The log file is opened in append-only mode. Writes a + as Wireshark. The log file is opened in append-only mode. Writes are synchronized between threads, but not between processes. .. versionadded:: 3.8 @@ -1980,78 +1959,6 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.7 -.. attribute:: SSLContext.msg_callback - - The message callback provides a debugging hook to analyze TLS connections. - The callback is called for any TLS protocol message (header, handshake, - alert, and more), but not for application data. Due to technical - limitations, the callback can't be used to filter traffic or to abort a - connection. Any exception raised in the callback is delayed until the - handshake, read, or write operation has been performed. - - Example:: - - def msg_cb(conn, direction, version, content_type, msg_type, data): - print(f"{direction:5} {version._name_:7} {content_type._name_:18} " - f"{msg_type._name_:20} {len(data):4}") - - ctx = ssl.create_default_context() - ctx.msg_callback = msg_cb - host = 'tls13.crypto.mozilla.org' - with ctx.wrap_socket(socket.socket(), server_hostname=host) as s: - s.connect((host, 443)) - s.sendall(b'HEAD / HTTP/1.1\r\n') - s.sendall(b'Host: %s\r\n\r\n' % host) - s.recv(4096) - s.unwrap() - - Output:: - - write TLSv1 HEADER HANDSHAKE 5 - write TLSv1_3 HANDSHAKE CLIENT_HELLO 512 - read TLSv1_2 HEADER HANDSHAKE 5 - read TLSv1_3 HANDSHAKE SERVER_HELLO 122 - read TLSv1_2 HEADER CHANGE_CIPHER_SPEC 5 - read TLSv1_2 HEADER APPLICATION_DATA 5 - read TLSv1_3 INNER_CONTENT_TYPE CERTIFICATE_STATUS 1 - read TLSv1_3 HANDSHAKE ENCRYPTED_EXTENSIONS 6 - read TLSv1_3 HANDSHAKE CERTIFICATE 3226 - read TLSv1_3 HANDSHAKE CERTIFICATE_VERIFY 79 - read TLSv1_3 HANDSHAKE FINISHED 52 - write TLSv1_2 HEADER CHANGE_CIPHER_SPEC 5 - write TLSv1_3 CHANGE_CIPHER_SPEC CHANGE_CIPHER_SPEC 1 - write TLSv1_2 HEADER APPLICATION_DATA 5 - write TLSv1_3 INNER_CONTENT_TYPE CERTIFICATE_STATUS 1 - write TLSv1_3 HANDSHAKE FINISHED 52 - write TLSv1_2 HEADER APPLICATION_DATA 5 - write TLSv1_3 INNER_CONTENT_TYPE SUPPLEMENTAL_DATA 1 - write TLSv1_2 HEADER APPLICATION_DATA 5 - write TLSv1_3 INNER_CONTENT_TYPE SUPPLEMENTAL_DATA 1 - read TLSv1_2 HEADER APPLICATION_DATA 5 - read TLSv1_3 INNER_CONTENT_TYPE CERTIFICATE_STATUS 1 - read TLSv1_3 HANDSHAKE NEWSESSION_TICKET 238 - read TLSv1_3 HANDSHAKE NEWSESSION_TICKET 238 - - - conn - :class:`SSLSocket` or :class:`SSLObject` instance - direction - ``read`` or ``write`` - version - :class:`TLSVersion` enum member or int for unknown version. For a - frame header, it's the header version. - content_type - :class:`TLSContentType` enum member or int for unsupported content type. - msg_type - Either a :class:`TLSContentType` enum number for a header message, - a :class:`TLSAlertType` enum member for an alert message, - a :class:`TLSMessageType` enum member for other messages, or int for - unsupported message types. - data - Raw, decrypted message content as bytes - - .. versionadded:: 3.8 - .. attribute:: SSLContext.options An integer representing the set of SSL options enabled on this context. diff --git a/Lib/ssl.py b/Lib/ssl.py index 4f3b0befbcc22c..f5fa6aeec2d21b 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -165,7 +165,7 @@ class TLSVersion(_IntEnum): MAXIMUM_SUPPORTED = _ssl.PROTO_MAXIMUM_SUPPORTED -class TLSContentType(_IntEnum): +class _TLSContentType(_IntEnum): """Content types (record layer) See RFC 8446, section B.1 @@ -179,7 +179,7 @@ class TLSContentType(_IntEnum): INNER_CONTENT_TYPE = 0x101 -class TLSAlertType(_IntEnum): +class _TLSAlertType(_IntEnum): """Alert types for TLSContentType.ALERT messages See RFC 8466, section B.2 @@ -220,7 +220,7 @@ class TLSAlertType(_IntEnum): NO_APPLICATION_PROTOCOL = 120 -class TLSMessageType(_IntEnum): +class _TLSMessageType(_IntEnum): """Message types (handshake protocol) See RFC 8446, section B.3 @@ -608,13 +608,48 @@ def hostname_checks_common_name(self): return True @property - def msg_callback(self): - return super().msg_callback.user_function + def _msg_callback(self): + """TLS message callback + + The message callback provides a debugging hook to analyze TLS + connections. The callback is called for any TLS protocol message + (header, handshake, alert, and more), but not for application data. + Due to technical limitations, the callback can't be used to filter + traffic or to abort a connection. Any exception raised in the + callback is delayed until the handshake, read, or write operation + has been performed. + + def msg_cb(conn, direction, version, content_type, msg_type, data): + pass + + conn + :class:`SSLSocket` or :class:`SSLObject` instance + direction + ``read`` or ``write`` + version + :class:`TLSVersion` enum member or int for unknown version. For a + frame header, it's the header version. + content_type + :class:`_TLSContentType` enum member or int for unsupported + content type. + msg_type + Either a :class:`_TLSContentType` enum number for a header + message, a :class:`_TLSAlertType` enum member for an alert + message, a :class:`_TLSMessageType` enum member for other + messages, or int for unsupported message types. + data + Raw, decrypted message content as bytes + """ + inner = super()._msg_callback + if inner is not None: + return inner.user_function + else: + return None - @msg_callback.setter - def msg_callback(self, callback): + @_msg_callback.setter + def _msg_callback(self, callback): if callback is None: - super(SSLContext, SSLContext).msg_callback.__set__(self, None) + super(SSLContext, SSLContext)._msg_callback.__set__(self, None) return if not hasattr(callback, '__call__'): @@ -627,16 +662,16 @@ def inner(conn, direction, version, content_type, msg_type, data): pass try: - content_type = TLSContentType(content_type) + content_type = _TLSContentType(content_type) except TypeError: pass - if content_type == TLSContentType.HEADER: - msg_enum = TLSContentType - elif content_type == TLSContentType.ALERT: - msg_enum = TLSAlertType + if content_type == _TLSContentType.HEADER: + msg_enum = _TLSContentType + elif content_type == _TLSContentType.ALERT: + msg_enum = _TLSAlertType else: - msg_enum = TLSMessageType + msg_enum = _TLSMessageType try: msg_type = msg_enum(msg_type) except TypeError: @@ -647,7 +682,7 @@ def inner(conn, direction, version, content_type, msg_type, data): inner.user_function = callback - super(SSLContext, SSLContext).msg_callback.__set__(self, inner) + super(SSLContext, SSLContext)._msg_callback.__set__(self, inner) @property def protocol(self): diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index d053575ef6cf0b..f368906c8a94f9 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -26,7 +26,7 @@ ssl = support.import_module("ssl") -from ssl import TLSVersion, TLSContentType, TLSMessageType, TLSAlertType +from ssl import TLSVersion, _TLSContentType, _TLSMessageType, _TLSAlertType PROTOCOLS = sorted(ssl._PROTOCOL_NAMES) HOST = support.HOST @@ -4493,6 +4493,18 @@ def test_keylog_env(self): ctx = ssl._create_stdlib_context() self.assertEqual(ctx.keylog_filename, support.TESTFN) + def test_msg_callback(self): + client_context, server_context, hostname = testing_context() + + def msg_cb(conn, direction, version, content_type, msg_type, data): + pass + + self.assertIs(client_context._msg_callback, None) + client_context._msg_callback = msg_cb + self.assertIs(client_context._msg_callback, msg_cb) + with self.assertRaises(TypeError): + client_context._msg_callback = object() + def test_msg_callback_tls12(self): client_context, server_context, hostname = testing_context() client_context.options |= ssl.OP_NO_TLSv1_3 @@ -4505,7 +4517,7 @@ def msg_cb(conn, direction, version, content_type, msg_type, data): self.assertIn(direction, {'read', 'write'}) msg.append((direction, version, content_type, msg_type)) - client_context.msg_callback = msg_cb + client_context._msg_callback = msg_cb server = ThreadedEchoServer(context=server_context, chatty=False) with server: @@ -4514,48 +4526,48 @@ def msg_cb(conn, direction, version, content_type, msg_type, data): s.connect((HOST, server.port)) self.assertEqual(msg, [ - ("write", TLSVersion.TLSv1, TLSContentType.HEADER, - TLSMessageType.CERTIFICATE_STATUS), - ("write", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, - TLSMessageType.CLIENT_HELLO), - ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, - TLSMessageType.CERTIFICATE_STATUS), - ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, - TLSMessageType.SERVER_HELLO), - ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, - TLSMessageType.CERTIFICATE_STATUS), - ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, - TLSMessageType.CERTIFICATE), - ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, - TLSMessageType.CERTIFICATE_STATUS), - ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, - TLSMessageType.SERVER_KEY_EXCHANGE), - ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, - TLSMessageType.CERTIFICATE_STATUS), - ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, - TLSMessageType.SERVER_DONE), - ("write", TLSVersion.TLSv1_2, TLSContentType.HEADER, - TLSMessageType.CERTIFICATE_STATUS), - ("write", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, - TLSMessageType.CLIENT_KEY_EXCHANGE), - ("write", TLSVersion.TLSv1_2, TLSContentType.HEADER, - TLSMessageType.FINISHED), - ("write", TLSVersion.TLSv1_2, TLSContentType.CHANGE_CIPHER_SPEC, - TLSMessageType.CHANGE_CIPHER_SPEC), - ("write", TLSVersion.TLSv1_2, TLSContentType.HEADER, - TLSMessageType.CERTIFICATE_STATUS), - ("write", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, - TLSMessageType.FINISHED), - ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, - TLSMessageType.CERTIFICATE_STATUS), - ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, - TLSMessageType.NEWSESSION_TICKET), - ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, - TLSMessageType.FINISHED), - ("read", TLSVersion.TLSv1_2, TLSContentType.HEADER, - TLSMessageType.CERTIFICATE_STATUS), - ("read", TLSVersion.TLSv1_2, TLSContentType.HANDSHAKE, - TLSMessageType.FINISHED), + ("write", TLSVersion.TLSv1, _TLSContentType.HEADER, + _TLSMessageType.CERTIFICATE_STATUS), + ("write", TLSVersion.TLSv1_2, _TLSContentType.HANDSHAKE, + _TLSMessageType.CLIENT_HELLO), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HEADER, + _TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HANDSHAKE, + _TLSMessageType.SERVER_HELLO), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HEADER, + _TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HANDSHAKE, + _TLSMessageType.CERTIFICATE), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HEADER, + _TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HANDSHAKE, + _TLSMessageType.SERVER_KEY_EXCHANGE), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HEADER, + _TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HANDSHAKE, + _TLSMessageType.SERVER_DONE), + ("write", TLSVersion.TLSv1_2, _TLSContentType.HEADER, + _TLSMessageType.CERTIFICATE_STATUS), + ("write", TLSVersion.TLSv1_2, _TLSContentType.HANDSHAKE, + _TLSMessageType.CLIENT_KEY_EXCHANGE), + ("write", TLSVersion.TLSv1_2, _TLSContentType.HEADER, + _TLSMessageType.FINISHED), + ("write", TLSVersion.TLSv1_2, _TLSContentType.CHANGE_CIPHER_SPEC, + _TLSMessageType.CHANGE_CIPHER_SPEC), + ("write", TLSVersion.TLSv1_2, _TLSContentType.HEADER, + _TLSMessageType.CERTIFICATE_STATUS), + ("write", TLSVersion.TLSv1_2, _TLSContentType.HANDSHAKE, + _TLSMessageType.FINISHED), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HEADER, + _TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HANDSHAKE, + _TLSMessageType.NEWSESSION_TICKET), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HEADER, + _TLSMessageType.FINISHED), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HEADER, + _TLSMessageType.CERTIFICATE_STATUS), + ("read", TLSVersion.TLSv1_2, _TLSContentType.HANDSHAKE, + _TLSMessageType.FINISHED), ]) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index a8af42343c6770..f40127d3d932bb 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4650,8 +4650,8 @@ static PyGetSetDef context_getsetlist[] = { {"keylog_filename", (getter) _PySSLContext_get_keylog_filename, (setter) _PySSLContext_set_keylog_filename, NULL}, #endif - {"msg_callback", (getter) _PySSLContext_get_msg_callback, - (setter) _PySSLContext_set_msg_callback, NULL}, + {"_msg_callback", (getter) _PySSLContext_get_msg_callback, + (setter) _PySSLContext_set_msg_callback, NULL}, {"sni_callback", (getter) get_sni_callback, (setter) set_sni_callback, PySSLContext_sni_callback_doc}, {"options", (getter) get_options,