Skip to content

Commit

Permalink
Merge 102cdcb into bb6142c
Browse files Browse the repository at this point in the history
  • Loading branch information
tomato42 committed Jun 24, 2015
2 parents bb6142c + 102cdcb commit 1d15e5e
Show file tree
Hide file tree
Showing 13 changed files with 1,044 additions and 95 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ clean:
rm -rf tlslite/utils/__pycache__
rm -rf tlslite/*.pyc
rm -rf tlslite/utils/*.pyc
rm -rf tlslite/integration/*.pyc
rm -rf tlslite/integration/*.pyc
rm -rf unit_tests/*.pyc
rm -rf unit_tests/__pycache__
rm -rf dist
Expand Down
3 changes: 3 additions & 0 deletions scripts/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
raise "This must be run as a command, not used as a module!"

from tlslite.api import *
from tlslite.constants import CipherSuite
from tlslite import __version__

try:
Expand Down Expand Up @@ -164,6 +165,8 @@ def printGoodConnection(connection, seconds):
print(" Version: %s" % connection.getVersionName())
print(" Cipher: %s %s" % (connection.getCipherName(),
connection.getCipherImplementation()))
print(" Ciphersuite: {0}".\
format(CipherSuite.ietfNames[connection.session.cipherSuite]))
if connection.session.srpUsername:
print(" Client SRP username: %s" % connection.session.srpUsername)
if connection.session.clientCertChain:
Expand Down
96 changes: 96 additions & 0 deletions tlslite/handshakehashes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Copyright (c) 2015, Hubert Kario
#
# See the LICENSE file for legal information regarding use of this file.
"""Handling cryptographic hashes for handshake protocol"""

from .utils.compat import compat26Str, compatHMAC
from .utils.cryptomath import MD5, SHA1
import hashlib

class HandshakeHashes(object):

"""
Store and calculate necessary hashes for handshake protocol
Calculates message digests of messages exchanged in handshake protocol
of SSLv3 and TLS.
"""

def __init__(self):
"""Create instance"""
self._handshakeMD5 = hashlib.md5()
self._handshakeSHA = hashlib.sha1()
self._handshakeSHA256 = hashlib.sha256()

def update(self, data):
"""
Add L{data} to hash input.
@type data: bytearray
@param data: serialized TLS handshake message
"""
text = compat26Str(data)
self._handshakeMD5.update(text)
self._handshakeSHA.update(text)
self._handshakeSHA256.update(text)

def digest(self, version):
"""
Calculate and return digest for the already consumed data.
Used for Finished and CertificateVerify messages.
@type version: tuple
@param version: TLS version tuple
"""
if version in [(3, 1), (3, 2)]:
return self._handshakeMD5.digest() + self._handshakeSHA.digest()
elif version in [(3, 3)]:
# TODO: return SHA384 for specific ciphers
return self._handshakeSHA256.digest()
else:
raise ValueError("Unknown protocol version")

def digestSSL(self, masterSecret, label):
"""
Calculate and return digest for already consumed data (SSLv3 version)
Used for Finished and CertificateVerify messages.
@type masterSecret: bytearray
@param masterSecret: value of the master secret
@type label: bytearray
@param label: label to include in the calculation
"""
#pylint: disable=maybe-no-member
imacMD5 = self._handshakeMD5.copy()
imacSHA = self._handshakeSHA.copy()
#pylint: enable=maybe-no-member

# the below difference in input for MD5 and SHA-1 is why we can't reuse
# digest() method
imacMD5.update(compatHMAC(label + masterSecret + bytearray([0x36]*48)))
imacSHA.update(compatHMAC(label + masterSecret + bytearray([0x36]*40)))

md5Bytes = MD5(masterSecret + bytearray([0x5c]*48) + \
bytearray(imacMD5.digest()))
shaBytes = SHA1(masterSecret + bytearray([0x5c]*40) + \
bytearray(imacSHA.digest()))

return md5Bytes + shaBytes

#pylint: disable=protected-access, maybe-no-member
def copy(self):
"""
Copy object
Return a copy of the object with all the hashes in the same state
as the source object.
@rtype: HandshakeHashes
"""
other = HandshakeHashes()
other._handshakeMD5 = self._handshakeMD5.copy()
other._handshakeSHA = self._handshakeSHA.copy()
other._handshakeSHA256 = self._handshakeSHA256.copy()
return other
6 changes: 4 additions & 2 deletions tlslite/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -769,16 +769,18 @@ def next_protos_advertised(self, val):
self.next_protos = val

def create(self, version, random, session_id, cipher_suite,
certificate_type, tackExt, next_protos_advertised,
certificate_type=None, tackExt=None, next_protos_advertised=None,
extensions=None):
"""Initialize the object for deserialisation"""
self.extensions = extensions
self.server_version = version
self.random = random
self.session_id = session_id
self.cipher_suite = cipher_suite
self.certificate_type = certificate_type
self.compression_method = 0
self.tackExt = tackExt
if tackExt is not None:
self.tackExt = tackExt
self.next_protos_advertised = next_protos_advertised
return self

Expand Down
185 changes: 185 additions & 0 deletions tlslite/messagesocket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# vim: set fileencoding=utf8
#
# Copyright © 2015, Hubert Kario
#
# See the LICENSE file for legal information regarding use of this file.

"""Wrapper of TLS RecordLayer providing message-level abstraction"""

from .recordlayer import RecordLayer
from .constants import ContentType
from .messages import RecordHeader3, Message
from .utils.codec import Parser

class MessageSocket(RecordLayer):

"""TLS Record Layer socket that provides Message level abstraction
Because the record layer has a hard size limit on sent messages, they need
to be fragmented before sending. Similarly, a single record layer record
can include multiple handshake protocol messages (very common with
ServerHello, Certificate and ServerHelloDone), as such, the user of
RecordLayer needs to fragment those records into multiple messages.
Unfortunately, fragmentation of messages requires some degree of
knowledge about the messages passed and as such is outside scope of pure
record layer implementation.
This class tries to provide a useful abstraction for handling Handshake
protocol messages.
@type recordSize: int
@ivar recordSize: maximum size of records sent through socket. Messages
bigger than this size will be fragmented to smaller chunks. Setting it
to higher value than the default 2^14 will make the implementation
non RFC compliant and likely not interoperable with other peers.
@type defragmenter: L{Defragmenter}
@ivar defragmenter: defragmenter used for read records
@type unfragmentedDataTypes: tuple
@ivar unfragmentedDataTypes: data types which will be passed as-read,
TLS application_data by default
"""

def __init__(self, sock, defragmenter):
"""Apply TLS Record Layer abstraction to raw network socket.
@type sock: L{socket.socket}
@param sock: network socket to wrap
@type defragmenter: L{Defragmenter}
@param defragmenter: defragmenter to apply on the records read
"""
super(MessageSocket, self).__init__(sock)

self.defragmenter = defragmenter
self.unfragmentedDataTypes = tuple((ContentType.application_data, ))
self._lastRecordVersion = (0, 0)

self._sendBuffer = bytearray(0)
self._sendBufferType = None

self.recordSize = 2**14

def recvMessage(self):
"""
Read next message in queue
will return a 0 or 1 if the read is blocking, a tuple of
L{RecordHeader3} and L{Parser} in case a message was received.
@rtype: generator
"""
while True:
while True:
ret = self.defragmenter.getMessage()
if ret is None:
break
header = RecordHeader3().create(self._lastRecordVersion,
ret[0],
0)
yield header, Parser(ret[1])

for ret in self.recvRecord():
if ret in (0, 1):
yield ret
else:
break

header, parser = ret
if header.type in self.unfragmentedDataTypes:
yield ret
# TODO probably needs a bit better handling...
if header.ssl2:
yield ret

self.defragmenter.addData(header.type, parser.bytes)
self._lastRecordVersion = header.version

def recvMessageBlocking(self):
"""Blocking variant of L{recvMessage}"""
for res in self.recvMessage():
if res in (0, 1):
pass
else:
return res

def flush(self):
"""
Empty the queue of messages to write
Will fragment the messages and write them in as little records as
possible.
@rtype: generator
"""
while len(self._sendBuffer) > 0:
recordPayload = self._sendBuffer[:self.recordSize]
self._sendBuffer = self._sendBuffer[self.recordSize:]
msg = Message(self._sendBufferType, recordPayload)
for res in self.sendRecord(msg):
yield res

assert len(self._sendBuffer) == 0
self._sendBufferType = None

def flushBlocking(self):
"""Blocking variant of L{flush}"""
for _ in self.flush():
pass

def queueMessage(self, msg):
"""
Queue message for sending
If the message is of same type as messages in queue, the message is
just added to queue.
If the message is of different type as messages in queue, the queue is
flushed and then the message is queued.
@rtype: generator
"""
if self._sendBufferType is None:
self._sendBufferType = msg.contentType

if msg.contentType == self._sendBufferType:
self._sendBuffer += msg.write()
return

for res in self.flush():
yield res

assert self._sendBufferType is None
self._sendBufferType = msg.contentType
self._sendBuffer += msg.write()

def queueMessageBlocking(self, msg):
"""Blocking variant of L{queueMessage}"""
for _ in self.queueMessage(msg):
pass

def sendMessage(self, msg):
"""
Fragment and send a message.
If a messages already of same type reside in queue, the message if
first added to it and then the queue is flushed.
If the message is of different type than the queue, the queue is
flushed, the message is added to queue and the queue is flushed again.
Use the sendRecord() message if you want to send a message outside
the queue, or a message of zero size.
@rtype: generator
"""
for res in self.queueMessage(msg):
yield res

for res in self.flush():
yield res

def sendMessageBlocking(self, msg):
"""Blocking variant of L{sendMessage}"""
for _ in self.sendMessage(msg):
pass
11 changes: 7 additions & 4 deletions tlslite/recordlayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,9 +385,12 @@ def _encryptThenSeal(self, buf, contentType):

return buf

def sendMessage(self, msg):
def sendRecord(self, msg):
"""
Encrypt, MAC and send message through socket.
Encrypt, MAC and send arbitrary message as-is through socket.
Note that if the message was not fragmented to below 2**14 bytes
it will be rejected by the other connection side.
@param msg: TLS message to send
@type msg: ApplicationData, HandshakeMessage, etc.
Expand Down Expand Up @@ -561,9 +564,9 @@ def _decryptAndUnseal(self, recordType, buf):
raise TLSBadRecordMAC("Invalid tag, decryption failure")
return buf

def recvMessage(self):
def recvRecord(self):
"""
Read, decrypt and check integrity of message
Read, decrypt and check integrity of a single record
@rtype: tuple
@return: message header and decrypted message payload
Expand Down
4 changes: 2 additions & 2 deletions tlslite/tlsrecordlayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ def _sendMsgThroughSocket(self, msg):
"""Send message, handle errors"""

try:
for result in self._recordLayer.sendMessage(msg):
for result in self._recordLayer.sendRecord(msg):
if result in (0, 1):
yield result
except socket.error:
Expand Down Expand Up @@ -828,7 +828,7 @@ def _getNextRecordFromSocket(self):

try:
# otherwise... read the next record
for result in self._recordLayer.recvMessage():
for result in self._recordLayer.recvRecord():
if result in (0, 1):
yield result
else:
Expand Down
Loading

0 comments on commit 1d15e5e

Please sign in to comment.