diff options
author | Mike Crute <mcrute@gmail.com> | 2013-09-09 20:21:01 -0400 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2013-09-09 20:21:01 -0400 |
commit | 9503ffd82cc45af3c44f9ecf4574f79ab52153bd (patch) | |
tree | e0603d0b5a13b77850392df8a1d0675fcb7d6a94 | |
parent | d2d6bc8887aab1af4768ba893e1c7928df2873e4 (diff) | |
download | ubntmfi-9503ffd82cc45af3c44f9ecf4574f79ab52153bd.tar.bz2 ubntmfi-9503ffd82cc45af3c44f9ecf4574f79ab52153bd.tar.xz ubntmfi-9503ffd82cc45af3c44f9ecf4574f79ab52153bd.zip |
Finishing up writer side
-rw-r--r-- | inform.py | 147 |
1 files changed, 119 insertions, 28 deletions
@@ -1,13 +1,24 @@ | |||
1 | import json | ||
1 | import struct | 2 | import struct |
2 | import binascii | 3 | import binascii |
3 | from Crypto.Cipher import AES | 4 | from Crypto.Cipher import AES |
5 | from cStringIO import StringIO | ||
4 | 6 | ||
5 | 7 | ||
6 | class BinaryDataStream(object): | 8 | class 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 | ||
21 | class Cryptor(object): | 44 | class 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 | ||
40 | class InformParser(object): | ||
41 | 70 | ||
42 | PROTOCOL_MAGIC = 1414414933 | 71 | class 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 | |||
120 | class 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() |