# Author: Hubert Kario, (c) 2015
# Released under Gnu GPL v2.0, see LICENSE file for details
"""Main event loop for running test cases"""
from __future__ import print_function
import socket
from tlslite.messages import Message, Certificate, RecordHeader2
from tlslite.handshakehashes import HandshakeHashes
from tlslite.errors import TLSAbruptCloseError
from tlslite.constants import ContentType, HandshakeType, AlertLevel, \
AlertDescription, SSL2HandshakeType, CipherSuite, TLS_1_3_HRR
from .expect import ExpectClose, ExpectNoMessage, ExpectAlert
[docs]
class ConnectionState(object):
"""
Keeps the TLS connection state for sending of messages
:ivar ~tlslite.messagesocket.MessageSocket msg_sock: message level
abstraction for TLS Record Socket
:ivar handshake_hashes: all handshake messages hashed
:ivar handshake_messages: all hadshake messages exchanged between peers
:ivar key: various computed cryptographic keys, hashes and secrets related
to handshake and record layer
``premaster_secret`` - premaster secret from TLS 1.2 and earlier
``client finished handshake hashes`` -
:py:class:`~tlslite.handshakehashes.HandshakeHashes` object that has
the handshake hashes of last handshake (the only Handshake in TLS 1.3)
up to and including the client Finished; used for post-handshake
authentication
"""
def __init__(self):
"""Prepare object for keeping connection state"""
self.msg_sock = None
# cipher negotiated in connection
self.cipher = 0
# version proposed in client hello
self.client_version = (3, 3)
# version negotiated in connection
self.version = (3, 3)
# hashes of all handshake messages exchanged so far
self.handshake_hashes = HandshakeHashes()
# hash of all messages exchanged up to Certificate Verify, if CV was
# used on connection
self.certificate_verify_handshake_hashes = None
# all handshake messages exchanged so far
self.handshake_messages = []
# are we a client or server side of connection (influences just the
# way encryption and MAC keys are calculated)
self.client = True
# calculated value for premaster secret
self.key = {}
self.key['premaster_secret'] = bytearray(0)
self.key['client handshake traffic secret'] = bytearray(0)
# negotiated value for master secret
self.key['master_secret'] = bytearray(0)
# random values shared by peers
self.server_random = bytearray(0)
self.client_random = bytearray(0)
# session ID set by server
self.session_id = bytearray(0)
# Finished message data for secure renegotiation
self.key['client_verify_data'] = bytearray(0)
self.key['server_verify_data'] = bytearray(0)
# Whether we are currently resuming a previously negotiated session
self.resuming = False
# variable holding the intermediate state for DHE (and similar) key
# exchanges
self.key_exchange = None
# Whether the session we're currently using is using extended master
# secret calculation defined in RFC 7627
self.extended_master_secret = False
# Whether the session we're currently using is using
# EncryptThenMAC extension defined in RFC 7366
self.encrypt_then_mac = False
# list of tickets received from the server
self.session_tickets = []
# used to enforce record_size_limit in TLS 1.2 and earlier
self._peer_record_size_limit = None
self._our_record_size_limit = None
@property
def prf_name(self):
"""Return the name of the PRF used for session.
TLS 1.3 specific function
"""
if self.cipher in CipherSuite.sha384PrfSuites:
return 'sha384'
return 'sha256'
@property
def prf_size(self):
"""Return the size of the PRF output used for session.
TLS 1.3 specific function
"""
if self.cipher in CipherSuite.sha384PrfSuites:
return 48
return 32
[docs]
def get_server_public_key(self):
"""Extract server public key from server Certificate message"""
certificates = (msg for msg in self.handshake_messages if\
isinstance(msg, Certificate))
cert_message = next(certificates)
return cert_message.cert_chain.getEndEntityPublicKey()
[docs]
def get_last_message_of_type(self, msg_type):
"""Returns last handshake message of provided type"""
for msg in reversed(self.handshake_messages):
if isinstance(msg, msg_type):
return msg
return None
[docs]
def guess_response(content_type, data, ssl2=False):
"""Guess which kind of message is in the record layer payload"""
if content_type == ContentType.change_cipher_spec:
if len(data) != 1:
return "ChangeCipherSpec(invalid size)"
return "ChangeCipherSpec()"
elif content_type == ContentType.alert:
if len(data) < 2:
return "Alert(invalid size)"
return "Alert({0}, {1})".format(AlertLevel.toStr(data[0]),
AlertDescription.toStr(data[1]))
elif content_type == ContentType.handshake:
if not data:
return "Handshake(invalid size)"
if ssl2:
return "Handshake({0})".format(SSL2HandshakeType.toStr(data[0]))
else:
if data[0] == HandshakeType.server_hello and \
data[6:6+32] == TLS_1_3_HRR:
return "Handshake(server_hello, hello_retry_request)"
return "Handshake({0})".format(HandshakeType.toStr(data[0]))
elif content_type == ContentType.application_data:
return "ApplicationData(len={0})".format(len(data))
else:
return ("Message(content_type={0}, first_byte={1}, "
"len={2})").format(ContentType.toStr(content_type),
data[0],
len(data))
[docs]
class Runner(object):
"""Test if sending a set of commands returns expected values"""
def __init__(self, conversation):
"""Link conversation with runner"""
self.conversation = conversation
self.state = ConnectionState()
[docs]
def run(self):
"""Execute conversation"""
node = self.conversation
try:
while node is not None:
old_node = None
msg = None
if node.is_command():
# update connection state
node.process(self.state)
node = node.child
continue
elif node.is_expect():
if isinstance(node, ExpectNoMessage):
old_timeout = self.state.msg_sock.sock.gettimeout()
self.state.msg_sock.sock.settimeout(node.timeout)
# check peer response
try:
header, parser = self.state.msg_sock.\
recvMessageBlocking()
except (TLSAbruptCloseError, socket.error) as exc:
if isinstance(exc, socket.timeout) and \
isinstance(node, ExpectNoMessage):
# for ExpectNoMessage we have nothing to do
# but to continue
self.state.msg_sock.sock.settimeout(old_timeout)
node = node.child
continue
close_node = next((n for n in node.get_all_siblings()
if isinstance(n, ExpectClose)),
None)
# timeout will happen if the other side hanged, to
# try differentiated between (when no alerts are sent)
# allow for close only when the connection was actively
# closed
if close_node and not isinstance(exc, socket.timeout):
close_node.process(self.state, None)
node = close_node.child
continue
else:
if isinstance(exc, socket.timeout):
raise AssertionError(
"Timeout when waiting for peer message")
else:
raise AssertionError(
"Unexpected closure from peer")
msg = Message(header.type, parser.bytes)
old_node = node
node = next((proc for proc in node.get_all_siblings()
if proc.is_match(msg)), None)
if node is None:
raise AssertionError("Unexpected message from peer: " +
guess_response(\
msg.contentType,
msg.write(),
isinstance(header,
RecordHeader2)))
node.process(self.state, msg)
node = node.child
continue
elif node.is_generator():
# send message to peer
msg = node.generate(self.state)
try:
# sendMessageBlocking is buffered and fragmenting
# that means that 0-length messages would get lost
# so send them directly through record layer
if msg.write():
if node.queue:
self.state.msg_sock.queueMessageBlocking(msg)
else:
self.state.msg_sock.sendMessageBlocking(msg)
else:
for _ in self.state.msg_sock.sendRecord(msg):
# make the method into a blocking one
pass
except socket.error:
close_node = next(
(n for n in node.get_all_siblings()
if isinstance(n, (ExpectClose, ExpectAlert))),
None)
if close_node:
node = close_node.child
continue
else:
raise AssertionError("Unexpected closure from peer")
# allow generators to perform actions after the message
# was sent like updating handshake hashes
node.post_send(self.state)
node = node.child
continue
else:
raise AssertionError("Unknown decision tree node")
except:
if self.state.msg_sock:
self.state.msg_sock.sock.close()
# TODO put into a log
if node is None:
node = old_node
print("Error encountered while processing node " + str(node) +
" (child: " + str(node.child) + ") with last message " +
"being: " + repr(msg))
raise