forked from NullHypothesis/scramblesuit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
message.py
226 lines (163 loc) · 6.95 KB
/
message.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
"""
This module provides code to handle ScrambleSuit protocol messages.
The exported classes and functions provide interfaces to handle protocol
messages, check message headers for validity and create protocol messages out
of application data.
"""
import obfsproxy.common.log as logging
import obfsproxy.common.serialize as pack
import obfsproxy.transports.base as base
import mycrypto
import const
log = logging.get_obfslogger()
def createProtocolMessages( data, flags=const.FLAG_PAYLOAD ):
"""
Create protocol messages out of the given payload.
The given `data' is turned into a list of protocol messages with the given
`flags' set. The list is then returned. If possible, all messages fill
the MTU.
"""
messages = []
while len(data) > const.MPU:
messages.append(ProtocolMessage(data[:const.MPU], flags=flags))
data = data[const.MPU:]
messages.append(ProtocolMessage(data, flags=flags))
log.debug("Created %d protocol messages." % len(messages))
return messages
def getFlagNames( flags ):
"""
Return the flag name encoded in the integer `flags' as string.
This function is only useful for printing easy-to-read flag names in debug
log messages.
"""
if flags == 1:
return "PAYLOAD"
elif flags == 2:
return "NEW_TICKET"
elif flags == 4:
return "PRNG_SEED"
else:
return "Undefined"
def isSane( totalLen, payloadLen, flags ):
"""
Verifies whether the given header fields are sane.
The values of the fields `totalLen', `payloadLen' and `flags' are checked
for their sanity. If they are in the expected range, `True' is returned.
If any of these fields has an invalid value, `False' is returned.
"""
def isFine( length ):
"""
Check if the given length is fine.
"""
return True if (0 <= length <= const.MPU) else False
log.debug("Message header: totalLen=%d, payloadLen=%d, flags"
"=%s" % (totalLen, payloadLen, getFlagNames(flags)))
validFlags = [
const.FLAG_PAYLOAD,
const.FLAG_NEW_TICKET,
const.FLAG_PRNG_SEED,
]
return isFine(totalLen) and \
isFine(payloadLen) and \
totalLen >= payloadLen and \
(flags in validFlags)
class ProtocolMessage( object ):
"""
Represents a ScrambleSuit protocol message.
This class provides methods to deal with protocol messages. The methods
make it possible to add padding as well as to encrypt and authenticate
protocol messages.
"""
def __init__( self, payload="", paddingLen=0, flags=const.FLAG_PAYLOAD ):
"""
Initialises a ProtocolMessage object.
"""
payloadLen = len(payload)
if (payloadLen + paddingLen) > const.MPU:
raise base.PluggableTransportError("No overly long messages.")
self.totalLen = payloadLen + paddingLen
self.payloadLen = payloadLen
self.payload = payload
self.flags = flags
def encryptAndHMAC( self, crypter, hmacKey ):
"""
Encrypt and authenticate this protocol message.
This protocol message is encrypted using `crypter' and authenticated
using `hmacKey'. Finally, the encrypted message prepended by a
HMAC-SHA256-128 is returned and ready to be sent over the wire.
"""
encrypted = crypter.encrypt(pack.htons(self.totalLen) +
pack.htons(self.payloadLen) +
chr(self.flags) + self.payload +
(self.totalLen - self.payloadLen) * '\0')
hmac = mycrypto.HMAC_SHA256_128(hmacKey, encrypted)
return hmac + encrypted
def addPadding( self, paddingLen ):
"""
Add padding to this protocol message.
Padding is added to this protocol message. The exact amount is
specified by `paddingLen'.
"""
# The padding must not exceed the message size.
if (self.totalLen + paddingLen) > const.MPU:
raise base.PluggableTransportError("Can't pad more than the MTU.")
if paddingLen == 0:
return
log.debug("Adding %d bytes of padding to %d-byte message." %
(paddingLen, const.HDR_LENGTH + self.totalLen))
self.totalLen += paddingLen
def __len__( self ):
"""
Return the length of this protocol message.
"""
return const.HDR_LENGTH + self.totalLen
# Alias class name in order to provide a more intuitive API.
new = ProtocolMessage
class MessageExtractor( object ):
"""
Extracts ScrambleSuit protocol messages out of an encrypted stream.
"""
def __init__( self ):
"""
Initialise a new MessageExtractor object.
"""
self.recvBuf = ""
self.totalLen = None
self.payloadLen = None
self.flags = None
def extract( self, data, aes, hmacKey ):
"""
Extracts (i.e., decrypts and authenticates) protocol messages.
The raw `data' coming directly from the wire is decrypted using `aes'
and authenticated using `hmacKey'. The payload is then returned as
unencrypted protocol messages. In case of invalid headers or HMACs, an
exception is raised.
"""
self.recvBuf += data
msgs = []
# Keep trying to unpack as long as there is at least a header.
while len(self.recvBuf) >= const.HDR_LENGTH:
# If necessary, extract the header fields.
if self.totalLen == self.payloadLen == self.flags == None:
self.totalLen = pack.ntohs(aes.decrypt(self.recvBuf[16:18]))
self.payloadLen = pack.ntohs(aes.decrypt(self.recvBuf[18:20]))
self.flags = ord(aes.decrypt(self.recvBuf[20]))
if not isSane(self.totalLen, self.payloadLen, self.flags):
raise base.PluggableTransportError("Invalid header.")
# Parts of the message are still on the wire; waiting.
if (len(self.recvBuf) - const.HDR_LENGTH) < self.totalLen:
break
rcvdHMAC = self.recvBuf[0:const.HMAC_SHA256_128_LENGTH]
vrfyHMAC = mycrypto.HMAC_SHA256_128(hmacKey,
self.recvBuf[const.HMAC_SHA256_128_LENGTH:
(self.totalLen + const.HDR_LENGTH)])
if rcvdHMAC != vrfyHMAC:
raise base.PluggableTransportError("Invalid message HMAC.")
# Decrypt the message and remove it from the input buffer.
extracted = aes.decrypt(self.recvBuf[const.HDR_LENGTH:
(self.totalLen + const.HDR_LENGTH)])[:self.payloadLen]
msgs.append(ProtocolMessage(payload=extracted, flags=self.flags))
self.recvBuf = self.recvBuf[const.HDR_LENGTH + self.totalLen:]
# Protocol message processed; now reset length fields.
self.totalLen = self.payloadLen = self.flags = None
return msgs