-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
1,044 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.