mirror of https://github.com/python/cpython
bpo-34271: Add ssl debugging helpers (GH-10031)
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. The msg_callback and related enums are private members. The feature is designed for internal debugging and not for end users. Signed-off-by: Christian Heimes <christian@python.org>
This commit is contained in:
parent
e9b51c0ad8
commit
c7f7069e77
|
@ -139,6 +139,10 @@ purposes.
|
||||||
*cadata* is given) or uses :meth:`SSLContext.load_default_certs` to load
|
*cadata* is given) or uses :meth:`SSLContext.load_default_certs` to load
|
||||||
default CA certificates.
|
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::
|
.. note::
|
||||||
The protocol, options, cipher and other settings may change to more
|
The protocol, options, cipher and other settings may change to more
|
||||||
restrictive values anytime without prior deprecation. The values
|
restrictive values anytime without prior deprecation. The values
|
||||||
|
@ -172,6 +176,10 @@ purposes.
|
||||||
|
|
||||||
3DES was dropped from the default cipher string.
|
3DES was dropped from the default cipher string.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.8
|
||||||
|
|
||||||
|
Support for key logging to :envvar:`SSLKEYLOGFILE` was added.
|
||||||
|
|
||||||
|
|
||||||
Exceptions
|
Exceptions
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
@ -1056,6 +1064,7 @@ Constants
|
||||||
|
|
||||||
SSL 3.0 to TLS 1.3.
|
SSL 3.0 to TLS 1.3.
|
||||||
|
|
||||||
|
|
||||||
SSL Sockets
|
SSL Sockets
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -1901,6 +1910,20 @@ to speed up repeated connections from the same clients.
|
||||||
|
|
||||||
This features requires OpenSSL 0.9.8f or newer.
|
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 are
|
||||||
|
synchronized between threads, but not between processes.
|
||||||
|
|
||||||
|
.. versionadded:: 3.8
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This features requires OpenSSL 1.1.1 or newer.
|
||||||
|
|
||||||
.. attribute:: SSLContext.maximum_version
|
.. attribute:: SSLContext.maximum_version
|
||||||
|
|
||||||
A :class:`TLSVersion` enum member representing the highest supported
|
A :class:`TLSVersion` enum member representing the highest supported
|
||||||
|
|
172
Lib/ssl.py
172
Lib/ssl.py
|
@ -165,6 +165,90 @@ class TLSVersion(_IntEnum):
|
||||||
MAXIMUM_SUPPORTED = _ssl.PROTO_MAXIMUM_SUPPORTED
|
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":
|
if sys.platform == "win32":
|
||||||
from _ssl import enum_certificates, enum_crls
|
from _ssl import enum_certificates, enum_crls
|
||||||
|
|
||||||
|
@ -523,6 +607,83 @@ class SSLContext(_SSLContext):
|
||||||
def hostname_checks_common_name(self):
|
def hostname_checks_common_name(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
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):
|
||||||
|
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
|
@property
|
||||||
def protocol(self):
|
def protocol(self):
|
||||||
return _SSLMethod(super().protocol)
|
return _SSLMethod(super().protocol)
|
||||||
|
@ -576,6 +737,11 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None,
|
||||||
# CERT_OPTIONAL or CERT_REQUIRED. Let's try to load default system
|
# CERT_OPTIONAL or CERT_REQUIRED. Let's try to load default system
|
||||||
# root CA certificates for the given purpose. This may fail silently.
|
# root CA certificates for the given purpose. This may fail silently.
|
||||||
context.load_default_certs(purpose)
|
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
|
return context
|
||||||
|
|
||||||
def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=CERT_NONE,
|
def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=CERT_NONE,
|
||||||
|
@ -617,7 +783,11 @@ def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=CERT_NONE,
|
||||||
# CERT_OPTIONAL or CERT_REQUIRED. Let's try to load default system
|
# CERT_OPTIONAL or CERT_REQUIRED. Let's try to load default system
|
||||||
# root CA certificates for the given purpose. This may fail silently.
|
# root CA certificates for the given purpose. This may fail silently.
|
||||||
context.load_default_certs(purpose)
|
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
|
return context
|
||||||
|
|
||||||
# Used by http.client if no context is explicitly passed.
|
# Used by http.client if no context is explicitly passed.
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
import unittest.mock
|
||||||
from test import support
|
from test import support
|
||||||
import socket
|
import socket
|
||||||
import select
|
import select
|
||||||
|
@ -25,6 +26,7 @@ except ImportError:
|
||||||
|
|
||||||
ssl = support.import_module("ssl")
|
ssl = support.import_module("ssl")
|
||||||
|
|
||||||
|
from ssl import TLSVersion, _TLSContentType, _TLSMessageType, _TLSAlertType
|
||||||
|
|
||||||
PROTOCOLS = sorted(ssl._PROTOCOL_NAMES)
|
PROTOCOLS = sorted(ssl._PROTOCOL_NAMES)
|
||||||
HOST = support.HOST
|
HOST = support.HOST
|
||||||
|
@ -4405,6 +4407,170 @@ class TestPostHandshakeAuth(unittest.TestCase):
|
||||||
self.assertIn(b'WRONG_SSL_VERSION', s.recv(1024))
|
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(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
|
||||||
|
|
||||||
|
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):
|
def test_main(verbose=False):
|
||||||
if support.verbose:
|
if support.verbose:
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -4440,7 +4606,7 @@ def test_main(verbose=False):
|
||||||
tests = [
|
tests = [
|
||||||
ContextTests, BasicSocketTests, SSLErrorTests, MemoryBIOTests,
|
ContextTests, BasicSocketTests, SSLErrorTests, MemoryBIOTests,
|
||||||
SSLObjectTests, SimpleBackgroundTests, ThreadedTests,
|
SSLObjectTests, SimpleBackgroundTests, ThreadedTests,
|
||||||
TestPostHandshakeAuth
|
TestPostHandshakeAuth, TestSSLDebug
|
||||||
]
|
]
|
||||||
|
|
||||||
if support.is_resource_enabled('network'):
|
if support.is_resource_enabled('network'):
|
||||||
|
|
|
@ -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.
|
104
Modules/_ssl.c
104
Modules/_ssl.c
|
@ -185,6 +185,10 @@ static void _PySSLFixErrno(void) {
|
||||||
# define HAVE_NPN 0
|
# define HAVE_NPN 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if (OPENSSL_VERSION_NUMBER >= 0x10101000L) && !defined(LIBRESSL_VERSION_NUMBER)
|
||||||
|
#define HAVE_OPENSSL_KEYLOG 1
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef INVALID_SOCKET /* MS defines this */
|
#ifndef INVALID_SOCKET /* MS defines this */
|
||||||
#define INVALID_SOCKET (-1)
|
#define INVALID_SOCKET (-1)
|
||||||
#endif
|
#endif
|
||||||
|
@ -423,6 +427,11 @@ typedef struct {
|
||||||
int protocol;
|
int protocol;
|
||||||
#ifdef TLS1_3_VERSION
|
#ifdef TLS1_3_VERSION
|
||||||
int post_handshake_auth;
|
int post_handshake_auth;
|
||||||
|
#endif
|
||||||
|
PyObject *msg_cb;
|
||||||
|
#ifdef HAVE_OPENSSL_KEYLOG
|
||||||
|
PyObject *keylog_filename;
|
||||||
|
BIO *keylog_bio;
|
||||||
#endif
|
#endif
|
||||||
} PySSLContext;
|
} PySSLContext;
|
||||||
|
|
||||||
|
@ -444,6 +453,13 @@ typedef struct {
|
||||||
PyObject *owner; /* Python level "owner" passed to servername callback */
|
PyObject *owner; /* Python level "owner" passed to servername callback */
|
||||||
PyObject *server_hostname;
|
PyObject *server_hostname;
|
||||||
_PySSLError err; /* last seen error from various sources */
|
_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;
|
} PySSLSocket;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -517,6 +533,8 @@ typedef enum {
|
||||||
#define GET_SOCKET_TIMEOUT(sock) \
|
#define GET_SOCKET_TIMEOUT(sock) \
|
||||||
((sock != NULL) ? (sock)->sock_timeout : 0)
|
((sock != NULL) ? (sock)->sock_timeout : 0)
|
||||||
|
|
||||||
|
#include "_ssl/debughelpers.c"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SSL errors.
|
* SSL errors.
|
||||||
*/
|
*/
|
||||||
|
@ -703,6 +721,18 @@ fail:
|
||||||
Py_XDECREF(verify_obj);
|
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 *
|
static PyObject *
|
||||||
PySSL_SetError(PySSLSocket *sslsock, int ret, const char *filename, int lineno)
|
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);
|
fill_and_set_sslerror(sslsock, type, p, errstr, lineno, e);
|
||||||
ERR_clear_error();
|
ERR_clear_error();
|
||||||
|
PySSL_ChainExceptions(sslsock);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -903,6 +934,9 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
|
||||||
self->owner = NULL;
|
self->owner = NULL;
|
||||||
self->server_hostname = NULL;
|
self->server_hostname = NULL;
|
||||||
self->err = err;
|
self->err = err;
|
||||||
|
self->exc_type = NULL;
|
||||||
|
self->exc_value = NULL;
|
||||||
|
self->exc_tb = NULL;
|
||||||
|
|
||||||
/* Make sure the SSL error state is initialized */
|
/* Make sure the SSL error state is initialized */
|
||||||
ERR_clear_error();
|
ERR_clear_error();
|
||||||
|
@ -1052,11 +1086,12 @@ _ssl__SSLSocket_do_handshake_impl(PySSLSocket *self)
|
||||||
Py_XDECREF(sock);
|
Py_XDECREF(sock);
|
||||||
if (ret < 1)
|
if (ret < 1)
|
||||||
return PySSL_SetError(self, ret, __FILE__, __LINE__);
|
return PySSL_SetError(self, ret, __FILE__, __LINE__);
|
||||||
|
if (PySSL_ChainExceptions(self) < 0)
|
||||||
|
return NULL;
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
Py_XDECREF(sock);
|
Py_XDECREF(sock);
|
||||||
|
PySSL_ChainExceptions(self);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2151,8 +2186,26 @@ PyDoc_STRVAR(PySSL_get_owner_doc,
|
||||||
"The Python-level owner of this object.\
|
"The Python-level owner of this object.\
|
||||||
Passed as \"self\" in servername callback.");
|
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 void PySSL_dealloc(PySSLSocket *self)
|
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)
|
||||||
{
|
{
|
||||||
if (self->ssl)
|
if (self->ssl)
|
||||||
SSL_free(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);
|
err.ssl == SSL_ERROR_WANT_WRITE);
|
||||||
|
|
||||||
Py_XDECREF(sock);
|
Py_XDECREF(sock);
|
||||||
if (len > 0)
|
if (len <= 0)
|
||||||
return PyLong_FromLong(len);
|
|
||||||
else
|
|
||||||
return PySSL_SetError(self, len, __FILE__, __LINE__);
|
return PySSL_SetError(self, len, __FILE__, __LINE__);
|
||||||
|
if (PySSL_ChainExceptions(self) < 0)
|
||||||
|
return NULL;
|
||||||
|
return PyLong_FromLong(len);
|
||||||
error:
|
error:
|
||||||
Py_XDECREF(sock);
|
Py_XDECREF(sock);
|
||||||
|
PySSL_ChainExceptions(self);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2486,6 +2540,8 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, int len, int group_right_1,
|
||||||
PySSL_SetError(self, count, __FILE__, __LINE__);
|
PySSL_SetError(self, count, __FILE__, __LINE__);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
if (self->exc_type != NULL)
|
||||||
|
goto error;
|
||||||
|
|
||||||
done:
|
done:
|
||||||
Py_XDECREF(sock);
|
Py_XDECREF(sock);
|
||||||
|
@ -2498,6 +2554,7 @@ done:
|
||||||
}
|
}
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
PySSL_ChainExceptions(self);
|
||||||
Py_XDECREF(sock);
|
Py_XDECREF(sock);
|
||||||
if (!group_right_1)
|
if (!group_right_1)
|
||||||
Py_XDECREF(dest);
|
Py_XDECREF(dest);
|
||||||
|
@ -2601,11 +2658,13 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
|
||||||
/* Retain the SSL error code */
|
/* Retain the SSL error code */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
Py_XDECREF(sock);
|
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)
|
if (sock)
|
||||||
/* It's already INCREF'ed */
|
/* It's already INCREF'ed */
|
||||||
return (PyObject *) sock;
|
return (PyObject *) sock;
|
||||||
|
@ -2614,6 +2673,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
|
||||||
|
|
||||||
error:
|
error:
|
||||||
Py_XDECREF(sock);
|
Py_XDECREF(sock);
|
||||||
|
PySSL_ChainExceptions(self);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2889,8 +2949,8 @@ static PyTypeObject PySSLSocket_Type = {
|
||||||
0, /*tp_as_buffer*/
|
0, /*tp_as_buffer*/
|
||||||
Py_TPFLAGS_DEFAULT, /*tp_flags*/
|
Py_TPFLAGS_DEFAULT, /*tp_flags*/
|
||||||
0, /*tp_doc*/
|
0, /*tp_doc*/
|
||||||
0, /*tp_traverse*/
|
(traverseproc) PySSL_traverse, /*tp_traverse*/
|
||||||
0, /*tp_clear*/
|
(inquiry) PySSL_clear, /*tp_clear*/
|
||||||
0, /*tp_richcompare*/
|
0, /*tp_richcompare*/
|
||||||
0, /*tp_weaklistoffset*/
|
0, /*tp_weaklistoffset*/
|
||||||
0, /*tp_iter*/
|
0, /*tp_iter*/
|
||||||
|
@ -3002,6 +3062,11 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
|
||||||
self->ctx = ctx;
|
self->ctx = ctx;
|
||||||
self->hostflags = X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
|
self->hostflags = X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
|
||||||
self->protocol = proto_version;
|
self->protocol = proto_version;
|
||||||
|
self->msg_cb = NULL;
|
||||||
|
#ifdef HAVE_OPENSSL_KEYLOG
|
||||||
|
self->keylog_filename = NULL;
|
||||||
|
self->keylog_bio = NULL;
|
||||||
|
#endif
|
||||||
#if HAVE_NPN
|
#if HAVE_NPN
|
||||||
self->npn_protocols = NULL;
|
self->npn_protocols = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
@ -3127,6 +3192,7 @@ context_traverse(PySSLContext *self, visitproc visit, void *arg)
|
||||||
#ifndef OPENSSL_NO_TLSEXT
|
#ifndef OPENSSL_NO_TLSEXT
|
||||||
Py_VISIT(self->set_sni_cb);
|
Py_VISIT(self->set_sni_cb);
|
||||||
#endif
|
#endif
|
||||||
|
Py_VISIT(self->msg_cb);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3135,6 +3201,16 @@ context_clear(PySSLContext *self)
|
||||||
{
|
{
|
||||||
#ifndef OPENSSL_NO_TLSEXT
|
#ifndef OPENSSL_NO_TLSEXT
|
||||||
Py_CLEAR(self->set_sni_cb);
|
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
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -4570,6 +4646,12 @@ static PyGetSetDef context_getsetlist[] = {
|
||||||
{"maximum_version", (getter) get_maximum_version,
|
{"maximum_version", (getter) get_maximum_version,
|
||||||
(setter) set_maximum_version, NULL},
|
(setter) set_maximum_version, NULL},
|
||||||
#endif
|
#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,
|
{"sni_callback", (getter) get_sni_callback,
|
||||||
(setter) set_sni_callback, PySSLContext_sni_callback_doc},
|
(setter) set_sni_callback, PySSLContext_sni_callback_doc},
|
||||||
{"options", (getter) get_options,
|
{"options", (getter) get_options,
|
||||||
|
|
|
@ -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
|
12
setup.py
12
setup.py
|
@ -2178,11 +2178,13 @@ class PyBuildExt(build_ext):
|
||||||
ssl_incs.extend(krb5_h)
|
ssl_incs.extend(krb5_h)
|
||||||
|
|
||||||
if config_vars.get("HAVE_X509_VERIFY_PARAM_SET1_HOST"):
|
if config_vars.get("HAVE_X509_VERIFY_PARAM_SET1_HOST"):
|
||||||
self.add(Extension('_ssl', ['_ssl.c'],
|
self.add(Extension(
|
||||||
include_dirs=openssl_includes,
|
'_ssl', ['_ssl.c'],
|
||||||
library_dirs=openssl_libdirs,
|
include_dirs=openssl_includes,
|
||||||
libraries=openssl_libs,
|
library_dirs=openssl_libdirs,
|
||||||
depends=['socketmodule.h']))
|
libraries=openssl_libs,
|
||||||
|
depends=['socketmodule.h', '_ssl/debughelpers.c'])
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.missing.append('_ssl')
|
self.missing.append('_ssl')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue