-
Notifications
You must be signed in to change notification settings - Fork 10
/
proto.py
198 lines (138 loc) · 5.25 KB
/
proto.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
"""Protocol of communication.
This module contain classes for generating and encrypting messages.
"""
import binascii
import hmac
import math
from hashlib import pbkdf2_hmac, sha256
from Crypto.Cipher import AES
class Protocol:
"""Protocol encoding/decoding for Norton Core WiFi router."""
@staticmethod
def encode_set_setting(setting_type, value, serial_number, init_vec):
"""Construct a message to set a setting.
:param setting_type: Setting type, see paper for details.
:param value: Value of the setting.
:param serial_number: Device serial number, uses as key for encryption.
:param init_vec: IV for encryption.
:return: Constructed message.
"""
enc = Encryption(serial_number, init_vec)
data_set = DataSet(setting_type, value, enc)
return data_set.get_whole_request()
@staticmethod
def encode_request_unlock(serial_number, init_vec):
"""Construct a message to unlock Norton Core Secure Router BLE channel. Format of message:
[type_of_message, length_of_data, nonce] ([0x06, 0x10, nonce]).
:param serial_number: Device serial number, uses as key for encryption.
:param init_vec: IV for encryption.
:return: Constructed message.
"""
enc = Encryption(serial_number, init_vec)
msg = bytearray([0x06, 0x10]) + enc.get_nonce()
return msg
@staticmethod
def encode_request_iv(protocol_version):
"""Construct a message to get IV for encryption.
:param protocol_version: Protocol version of BLE communication.
:return: Constructed message.
"""
protocol_version = float(protocol_version)
minor_ver, major_ver = math.modf(protocol_version)
return bytearray([0x00, int(major_ver), int(minor_ver * 10)])
@staticmethod
def decode_ack(buffer):
"""Decode a Ack response.
:param buffer: Message (Ack response) for decoding.
:return: Length of sent data.
"""
return buffer[2]
@staticmethod
def decode_iv(buffer):
"""Decode a message with IV.
:param buffer: Message for decoding.
:return: Initialization vector for encryption.
"""
return buffer[2:18]
class DataSet:
"""Class for generating encrypted message for set a setting: username, password, IP, DNS and
etc.
"""
_type_request = 0x01
def __init__(self, setting_type, value, encryptor):
"""
:param setting_type: Type of setting.
:param value: Value of setting.
:param encryptor: Encryption object for encrypting message.
"""
value = bytes(value, 'utf-8')
self._setting_type = setting_type
self._data = value
self._data += sha256(value).digest()[:4]
self._data += self._get_padding(self._data)
self._data = encryptor.encrypt(self._data)
self._total_len = len(self._data)
self._sent_len = 0
def get_chunk_request(self):
"""Get part of request. Uses for testing.
:return: Chunk of request.
"""
if self._sent_len == self._total_len:
return None
packet_len = min(self._total_len - self._sent_len, 13)
packet = bytearray(2 + 3)
packet[0] = self._type_request
packet[1] = packet_len + 3 | 0x80
packet[2] = packet_len + self._sent_len
packet[3] = self._total_len
packet[4] = self._setting_type
msg = self._data[self._sent_len:self._sent_len + packet_len]
packet += msg
self._sent_len += packet_len
return packet
def get_whole_request(self):
"""Get whole request without splitting.
:return: Whole request.
"""
request = bytearray()
chunk_request = self.get_chunk_request()
while chunk_request:
request += chunk_request
chunk_request = self.get_chunk_request()
return request
@staticmethod
def _get_padding(data):
length = 16 - (len(data) % 16)
return bytes([length]) * length
class Encryption:
"""Class uses for getting nonce, keys and encrypt messages."""
def __init__(self, serial_number, iv):
"""
:param serial_number: Device serial number, uses as key.
:param iv: Initialization vector.
"""
self._pwd = bytes(serial_number, 'utf-8')
self._iv = iv
self._hmac = hmac.new(self._iv, self._pwd, sha256).digest()[-16:]
self._dk = pbkdf2_hmac('sha256', self._pwd, self._iv, 1000, 16)
def __str__(self) -> str:
return "Enc{{\npwd: {}\
\niv: {}\
\nhmac: {}\
\ndk: {}\
\n}}".format(self._pwd,
binascii.hexlify(self._iv),
binascii.hexlify(self._hmac),
binascii.hexlify(self._dk))
def get_nonce(self):
"""Get nonce (HMAC) for unlocking Norton Core Router.
:return: Generated HMAC.
"""
return self._hmac
def encrypt(self, data):
"""Encrypt data with AES, key - device serial number.
:param data: Plain text data.
:return: Encrypted data.
"""
aes_enc = AES.new(self._dk, AES.MODE_CBC, bytes(self._iv))
return aes_enc.encrypt(data)