This repository has been archived on 2024-11-30. You can view files and clone it, but cannot push or open issues or pull requests.
PyBitmessage-2024-11-30/src/network/tls.py

247 lines
9.5 KiB
Python
Raw Normal View History

"""
SSL/TLS negotiation.
"""
import logging
2017-04-16 18:27:15 +02:00
import os
import socket
import ssl
import sys
2017-04-16 18:27:15 +02:00
import network.asyncore_pollchoose as asyncore
import paths
from network.advanceddispatcher import AdvancedDispatcher
from queues import receiveDataQueue
logger = logging.getLogger('default')
_DISCONNECTED_SSL = frozenset((ssl.SSL_ERROR_EOF,))
# sslProtocolVersion
if sys.version_info >= (2, 7, 13):
# this means TLSv1 or higher
# in the future change to
# ssl.PROTOCOL_TLS1.2
sslProtocolVersion = ssl.PROTOCOL_TLS # pylint: disable=no-member
elif sys.version_info >= (2, 7, 9):
2019-12-19 12:24:53 +01:00
# this means any SSL/TLS.
# SSLv2 and 3 are excluded with an option after context is created
sslProtocolVersion = ssl.PROTOCOL_SSLv23
else:
# this means TLSv1, there is no way to set "TLSv1 or higher" or
# "TLSv1.2" in < 2.7.9
sslProtocolVersion = ssl.PROTOCOL_TLSv1
# ciphers
2019-12-19 12:24:53 +01:00
if ssl.OPENSSL_VERSION_NUMBER >= 0x10100000 and not \
ssl.OPENSSL_VERSION.startswith("LibreSSL"):
sslProtocolCiphers = "AECDH-AES256-SHA@SECLEVEL=0"
else:
sslProtocolCiphers = "AECDH-AES256-SHA"
class TLSDispatcher(AdvancedDispatcher):
2019-09-11 12:10:42 +02:00
"""TLS functionality for classes derived from AdvancedDispatcher"""
2019-12-19 12:24:53 +01:00
# pylint: disable=too-many-instance-attributes, too-many-arguments
# pylint: disable=super-init-not-called
def __init__(self, _=None, sock=None, certfile=None, keyfile=None,
server_side=False, ciphers=sslProtocolCiphers):
self.want_read = self.want_write = True
2017-04-04 10:46:01 +02:00
if certfile is None:
2019-12-19 12:24:53 +01:00
self.certfile = os.path.join(
paths.codePath(), 'sslkeys', 'cert.pem')
2017-04-04 10:46:01 +02:00
else:
self.certfile = certfile
if keyfile is None:
2019-12-19 12:24:53 +01:00
self.keyfile = os.path.join(
paths.codePath(), 'sslkeys', 'key.pem')
2017-04-04 10:46:01 +02:00
else:
self.keyfile = keyfile
self.server_side = server_side
self.ciphers = ciphers
2017-04-04 10:46:01 +02:00
self.tlsStarted = False
self.tlsDone = False
self.tlsVersion = "N/A"
2017-04-04 10:46:01 +02:00
self.isSSL = False
2017-04-04 10:46:01 +02:00
def state_tls_init(self):
2019-09-11 12:10:42 +02:00
"""Prepare sockets for TLS handshake"""
# pylint: disable=attribute-defined-outside-init
2017-04-04 10:46:01 +02:00
self.isSSL = True
2017-04-16 18:27:15 +02:00
self.tlsStarted = True
2019-12-19 12:24:53 +01:00
# Once the connection has been established,
# it's safe to wrap the socket.
2019-09-11 11:01:17 +02:00
if sys.version_info >= (2, 7, 9):
context = ssl.create_default_context(
2019-12-19 12:24:53 +01:00
purpose=ssl.Purpose.SERVER_AUTH
if self.server_side else ssl.Purpose.CLIENT_AUTH)
context.set_ciphers(self.ciphers)
2017-04-04 10:46:01 +02:00
context.set_ecdh_curve("secp256k1")
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
# also exclude TLSv1 and TLSv1.1 in the future
2019-09-11 11:01:17 +02:00
context.options = ssl.OP_ALL | ssl.OP_NO_SSLv2 |\
2019-12-19 12:24:53 +01:00
ssl.OP_NO_SSLv3 | ssl.OP_SINGLE_ECDH_USE |\
ssl.OP_CIPHER_SERVER_PREFERENCE
2019-09-11 11:01:17 +02:00
self.sslSocket = context.wrap_socket(
2019-12-19 12:24:53 +01:00
self.socket, server_side=self.server_side,
do_handshake_on_connect=False)
else:
self.sslSocket = ssl.wrap_socket(
self.socket, server_side=self.server_side,
ssl_version=sslProtocolVersion,
certfile=self.certfile, keyfile=self.keyfile,
ciphers=self.ciphers, do_handshake_on_connect=False)
self.sslSocket.setblocking(0)
self.want_read = self.want_write = True
self.set_state("tls_handshake")
return False
# if hasattr(self.socket, "context"):
# self.socket.context.set_ecdh_curve("secp256k1")
2019-09-11 12:10:42 +02:00
@staticmethod
def state_tls_handshake():
"""
Do nothing while TLS handshake is pending, as during this phase
we need to react to callbacks instead
"""
return False
def writable(self):
2019-09-11 12:10:42 +02:00
"""Handle writable checks for TLS-enabled sockets"""
try:
if self.tlsStarted and not self.tlsDone and not self.write_buf:
return self.want_write
2017-06-24 12:13:35 +02:00
return AdvancedDispatcher.writable(self)
except AttributeError:
2017-04-16 18:27:15 +02:00
return AdvancedDispatcher.writable(self)
def readable(self):
2019-09-11 12:10:42 +02:00
"""Handle readable check for TLS-enabled sockets"""
try:
2019-12-19 12:24:53 +01:00
# during TLS handshake, and after flushing write buffer,
# return status of last handshake attempt
if self.tlsStarted and not self.tlsDone and not self.write_buf:
logger.debug('tls readable, %r', self.want_read)
return self.want_read
2019-12-19 12:24:53 +01:00
# prior to TLS handshake,
# receiveDataThread should emulate synchronous behaviour
elif not self.fullyEstablished and (
self.expectBytes == 0 or not self.write_buf_empty()):
return False
2017-06-24 12:13:35 +02:00
return AdvancedDispatcher.readable(self)
except AttributeError:
2017-04-16 18:27:15 +02:00
return AdvancedDispatcher.readable(self)
def handle_read(self): # pylint: disable=inconsistent-return-statements
2019-09-11 12:10:42 +02:00
"""
Handle reads for sockets during TLS handshake. Requires special
treatment as during the handshake, buffers must remain empty
and normal reads must be ignored.
2019-09-11 12:10:42 +02:00
"""
try:
# wait for write buffer flush
if self.tlsStarted and not self.tlsDone and not self.write_buf:
2019-12-19 12:24:53 +01:00
# logger.debug(
# "%s:%i TLS handshaking (read)", self.destination.host,
# self.destination.port)
self.tls_handshake()
else:
2019-12-19 12:24:53 +01:00
# logger.debug(
# "%s:%i Not TLS handshaking (read)", self.destination.host,
# self.destination.port)
return AdvancedDispatcher.handle_read(self)
except AttributeError:
2017-04-16 18:27:15 +02:00
return AdvancedDispatcher.handle_read(self)
except ssl.SSLError as err:
self.close_reason = "SSL Error in handle_read"
if err.errno == ssl.SSL_ERROR_WANT_READ:
return
elif err.errno in _DISCONNECTED_SSL:
self.handle_close()
return
logger.info("SSL Error: %s", err)
self.handle_close()
return
def handle_write(self): # pylint: disable=inconsistent-return-statements
2019-09-11 12:10:42 +02:00
"""
Handle writes for sockets during TLS handshake. Requires special
treatment as during the handshake, buffers must remain empty
and normal writes must be ignored.
2019-09-11 12:10:42 +02:00
"""
try:
# wait for write buffer flush
if self.tlsStarted and not self.tlsDone and not self.write_buf:
2019-12-19 12:24:53 +01:00
# logger.debug(
# "%s:%i TLS handshaking (write)", self.destination.host,
# self.destination.port)
self.tls_handshake()
else:
2019-12-19 12:24:53 +01:00
# logger.debug(
# "%s:%i Not TLS handshaking (write)", self.destination.host,
# self.destination.port)
return AdvancedDispatcher.handle_write(self)
except AttributeError:
return AdvancedDispatcher.handle_write(self)
except ssl.SSLError as err:
self.close_reason = "SSL Error in handle_write"
if err.errno == ssl.SSL_ERROR_WANT_WRITE:
return 0
elif err.errno in _DISCONNECTED_SSL:
self.handle_close()
return 0
logger.info("SSL Error: %s", err)
self.handle_close()
return
def tls_handshake(self):
2019-09-11 12:10:42 +02:00
"""Perform TLS handshake and handle its stages"""
2017-04-16 18:27:15 +02:00
# wait for flush
2017-06-24 12:13:35 +02:00
if self.write_buf:
2017-04-16 18:27:15 +02:00
return False
# Perform the handshake.
try:
logger.debug("handshaking (internal)")
self.sslSocket.do_handshake()
2017-06-24 12:13:35 +02:00
except ssl.SSLError as err:
self.close_reason = "SSL Error in tls_handshake"
logger.info("%s:%i: handshake fail", *self.destination)
self.want_read = self.want_write = False
if err.args[0] == ssl.SSL_ERROR_WANT_READ:
logger.debug("want read")
self.want_read = True
if err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
logger.debug("want write")
self.want_write = True
if not (self.want_write or self.want_read):
raise
except socket.error as err:
# pylint: disable=protected-access
if err.errno in asyncore._DISCONNECTED:
self.close_reason = "socket.error in tls_handshake"
self.handle_close()
else:
raise
else:
if sys.version_info >= (2, 7, 9):
self.tlsVersion = self.sslSocket.version()
logger.debug(
'%s:%i: TLS handshake success, TLS protocol version: %s',
self.destination.host, self.destination.port,
self.tlsVersion)
else:
self.tlsVersion = "TLSv1"
logger.debug(
'%s:%i: TLS handshake success',
self.destination.host, self.destination.port)
# The handshake has completed, so remove this channel and...
self.del_channel()
self.set_socket(self.sslSocket)
self.tlsDone = True
self.bm_proto_reset()
self.set_state("connection_fully_established")
receiveDataQueue.put(self.destination)
2017-04-16 18:27:15 +02:00
return False