aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2013-09-09 20:21:01 -0400
committerMike Crute <mcrute@gmail.com>2013-09-09 20:21:01 -0400
commit9503ffd82cc45af3c44f9ecf4574f79ab52153bd (patch)
treee0603d0b5a13b77850392df8a1d0675fcb7d6a94
parentd2d6bc8887aab1af4768ba893e1c7928df2873e4 (diff)
downloadubntmfi-9503ffd82cc45af3c44f9ecf4574f79ab52153bd.tar.bz2
ubntmfi-9503ffd82cc45af3c44f9ecf4574f79ab52153bd.tar.xz
ubntmfi-9503ffd82cc45af3c44f9ecf4574f79ab52153bd.zip
Finishing up writer side
-rw-r--r--inform.py147
1 files changed, 119 insertions, 28 deletions
diff --git a/inform.py b/inform.py
index 8c4b3e0..1af39e6 100644
--- a/inform.py
+++ b/inform.py
@@ -1,13 +1,24 @@
1import json
1import struct 2import struct
2import binascii 3import binascii
3from Crypto.Cipher import AES 4from Crypto.Cipher import AES
5from cStringIO import StringIO
4 6
5 7
6class BinaryDataStream(object): 8class BinaryDataStream(object):
9 """Directional binary data stream
10
11 Reads and writes binary data from any stream-like object. This object is
12 not bi-directional. Does no interpertation just unpacking and packing.
13 """
7 14
8 def __init__(self, data): 15 def __init__(self, data):
9 self.data = data 16 self.data = data
10 17
18 @classmethod
19 def for_output(cls):
20 return cls(StringIO())
21
11 def read_int(self): 22 def read_int(self):
12 return struct.unpack(">i", self.data.read(4))[0] 23 return struct.unpack(">i", self.data.read(4))[0]
13 24
@@ -17,8 +28,25 @@ class BinaryDataStream(object):
17 def read_string(self, length): 28 def read_string(self, length):
18 return self.data.read(length) 29 return self.data.read(length)
19 30
31 def write_int(self, data):
32 self.data.write(struct.pack(">i", data))
33
34 def write_short(self, data):
35 self.data.write(struct.pack(">h", data))
36
37 def write_string(self, data):
38 self.data.write(data)
39
40 def get_output(self):
41 return self.data.getvalue()
42
20 43
21class Cryptor(object): 44class Cryptor(object):
45 """AES encryption strategy
46
47 Handles AES crypto by wrapping pycrypto. Does padding and un-padding as
48 well as key conversions when needed.
49 """
22 50
23 def __init__(self, key, iv): 51 def __init__(self, key, iv):
24 self.iv = iv 52 self.iv = iv
@@ -31,24 +59,28 @@ class Cryptor(object):
31 59
32 @staticmethod 60 @staticmethod
33 def pad(s, BS=16): 61 def pad(s, BS=16):
34 return s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 62 return s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
35 63
36 def decrypt(self, payload): 64 def decrypt(self, payload):
37 return self.unpad(self.cipher.decrypt(payload)) 65 return self.unpad(self.cipher.decrypt(payload))
38 66
67 def encrypt(self, payload):
68 return self.cipher.encrypt(self.pad(payload))
39 69
40class InformParser(object):
41 70
42 PROTOCOL_MAGIC = 1414414933 71class InformPacket(object):
43 MAX_VERSION = 1 72 """Inform model object
73
74 Holds basic, parsed, inform packet data. Does some interpertation for
75 fields like flags. Can be passed to and from the serialiser. This class
76 only fully supports version 1 of the inform data protocol. Version 0
77 payload parsing is not supported.
78 """
44 79
45 ENCRYPTED_FLAG = 0x1 80 ENCRYPTED_FLAG = 0x1
46 COMPRESSED_FLAG = 0x2 81 COMPRESSED_FLAG = 0x2
47 82
48 def __init__(self, input_stream, key): 83 def __init__(self):
49 self.input_stream = input_stream
50 self.key = key
51
52 self.magic_number = None 84 self.magic_number = None
53 self.version = None 85 self.version = None
54 self.mac_addr = None 86 self.mac_addr = None
@@ -56,15 +88,11 @@ class InformParser(object):
56 self.iv = None 88 self.iv = None
57 self.data_version = None 89 self.data_version = None
58 self.data_length = None 90 self.data_length = None
59 self.payload = None 91 self.raw_payload = None
60
61 @classmethod
62 def open(cls, filename, key):
63 return cls(BinaryDataStream(open(filename, "rb")), key)
64 92
65 @staticmethod 93 @staticmethod
66 def _format_mac_addr(mac_bytes): 94 def _format_mac_addr(mac_bytes):
67 return "-".join([binascii.hexlify(i) for i in mac_bytes]) 95 return ":".join([binascii.hexlify(i) for i in mac_bytes])
68 96
69 def _has_flag(self, flag): 97 def _has_flag(self, flag):
70 return self.flags & flag != 0 98 return self.flags & flag != 0
@@ -82,21 +110,84 @@ class InformParser(object):
82 return self._has_flag(self.COMPRESSED_FLAG) 110 return self._has_flag(self.COMPRESSED_FLAG)
83 111
84 @property 112 @property
85 def decrypted_payload(self): 113 def payload(self):
86 return Cryptor(self.key, self.iv).decrypt(self.payload) 114 if self.data_version == 1:
115 return json.loads(self.raw_payload)
116 else:
117 return self.raw_payload
118
119
120class InformSerializer(object):
121 """Inform protocol version 1 parser/serializer
122
123 Handles the parsing of the inform binary protocol to python objects and
124 seralization of python objects to inform binary protocol. Handles
125 cryptography and data formats. Compatible only with version 1 of the data
126 format.
127 """
128
129 MASTER_KEY = "ba86f2bbe107c7c57eb5f2690775c712"
130 PROTOCOL_MAGIC = 1414414933
131 MAX_VERSION = 1
132
133 def __init__(self, key=MASTER_KEY):
134 self.key = key
135 self._used_key = None
136
137 def _decrypt_payload(self, packet):
138 if not packet.is_encrypted:
139 return
140
141 for key in (self.key, self.MASTER_KEY):
142 decrypted = Cryptor(key, packet.iv).decrypt(packet.raw_payload)
143
144 try:
145 json.loads(decrypted)
146 packet.raw_payload = decrypted
147 self._used_key = key
148 break
149 except ValueError:
150 continue
151
152 def parse(self, input):
153 input_stream = BinaryDataStream(input)
154
155 packet = InformPacket()
156
157 packet.magic = input_stream.read_int()
158 assert packet.magic == self.PROTOCOL_MAGIC
159
160 packet.version = input_stream.read_int()
161 assert packet.version < self.MAX_VERSION
162
163 packet.mac_addr = input_stream.read_string(6)
164 packet.flags = input_stream.read_short()
165 packet.iv = input_stream.read_string(16)
166 packet.data_version = input_stream.read_int()
167 packet.data_length = input_stream.read_int()
168
169 packet.raw_payload = input_stream.read_string(packet.data_length)
170 self._decrypt_payload(packet)
171
172 return packet
173
174 def _encrypt_payload(self, packet):
175 if packet.data_version != 1:
176 raise ValueError("Can no encrypt contents of pre 1.0 packets")
87 177
88 def parse(self): 178 key = self._used_key if self._used_key else self.MASTER_KEY
89 self.magic = self.input_stream.read_int() 179 return Cryptor(key, packet.iv).encrypt(json.dumps(packet.payload))
90 assert self.magic == self.PROTOCOL_MAGIC
91 180
92 self.version = self.input_stream.read_int() 181 def serialize(self, packet):
93 assert self.version < self.MAX_VERSION 182 output = BinaryDataStream.for_output()
94 183
95 self.mac_addr = self.input_stream.read_string(6) 184 output.write_int(packet.magic)
96 self.flags = self.input_stream.read_short() 185 output.write_int(packet.version)
97 self.iv = self.input_stream.read_string(16) 186 output.write_string(packet.mac_addr)
98 self.data_version = self.input_stream.read_int() 187 output.write_short(packet.flags)
99 self.data_length = self.input_stream.read_int() 188 output.write_string(packet.iv)
100 self.payload = self.input_stream.read_string(self.data_length) 189 output.write_int(packet.data_version)
190 output.write_int(packet.data_length)
191 output.write_string(self._encrypt_payload(packet))
101 192
102 return self 193 return output.get_output()