mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00
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
7 changed files with 677 additions and 18 deletions
172
Lib/ssl.py
172
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,83 @@ class SSLContext(_SSLContext):
|
|||
def hostname_checks_common_name(self):
|
||||
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
|
||||
def protocol(self):
|
||||
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
|
||||
# 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 +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
|
||||
# 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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue