forked from PX4/PX4-Autopilot
-
Notifications
You must be signed in to change notification settings - Fork 4
/
signtool.py
349 lines (277 loc) · 11.1 KB
/
signtool.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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
#!/usr/bin/env python3
from os import EX_CANTCREAT
import nacl.encoding
import nacl.signing
import nacl.hash
import struct
import zlib
import json
import base64
from pathlib import Path
from dataclasses import dataclass
import cryptotools
import argparse
import os
# Dictionary describing the possible TOC flags, they can be OR'ed together
toc_flag_dict = {
'TOC_FLAG1_BOOT': 0x1,
'TOC_FLAG1_VTORS': 0x2,
'TOC_FLAG1_CHECK_SIGNATURE': 0x4,
'TOC_FLAG1_DECRYPT': 0x8,
'TOC_FLAG1_COPY': 0x10,
'TOC_FLAG1_RDCT': 0x80,
}
# Dataclasses describing the TOC data, used to parse the to and from binary
@dataclass
class TOC_start:
start_magic: str = None
version: int = None
STRUCT_STRUCTURE = "<4sI" # type defininig string from the STRUCT package
# Dataclass to describe the TOC for 2 entries, one describing the signature typen
# and the second one describing the signature location.
@dataclass
class TOC_entry:
toc_position: int = 0
app_name: str = None
app_start: int = None
app_end: int = None
app_target: int = None
app_signature_idx: int = None
app_signature_key: int = None
app_encryption_key: int = None
app_flags1: int = None
app_reserved: int = None
sig_name: str = None
sig_start: int = None
sig_end: int = None
sig_target: int = None
sig_signature_idx: int = None
sig_signature_key: int = None
sig_encryption_key: int = None
sig_flags1: int = None
sig_reserved: int = None
# type defininig string from the STRUCT package
STRUCT_STRUCTURE = "<4sIIIBBBBI4sIIIBBBBI"
def toc2bin(data):
'''
Takes as TOC data class and converts it to the binary representation.
Prepare a TOC_entry data class with values and hand it over to this function.
data: is a dataclass TOC_entry
retrun: a packed binary to add to the px4 bin file
'''
return struct.pack(data.STRUCT_STRUCTURE,
data.app_name, data.app_start,
data.app_end, data.app_target,
data.app_signature_idx,
data.app_signature_key,
data.app_encryption_key,
data.app_flags1,
data.app_reserved,
data.sig_name, data.sig_start,
data.sig_end, data.sig_target,
data.sig_signature_idx,
data.sig_signature_key,
data.sig_encryption_key,
data.sig_flags1,
data.sig_reserved)
def bin2toc_start(bin):
'''
Takes binary data and unpacks a Toc_start header.
bin: binary data to parse for the TOC header.
return: a dataclass TOC_start
'''
data = TOC_start()
(data.start_magic, data.version) = struct.unpack(data.STRUCT_STRUCTURE, bin)
return data
def bin2toc_entry(bin):
'''
Takes binary data and unpacks it into a TOC entry dataclass.
bin: Binary data to unpack the TOC entry from.
return: A dataclass of TOC_entry
'''
data = TOC_entry()
(data.app_name, data.app_start,
data.app_end, data.app_target,
data.app_signature_idx,
data.app_signature_key,
data.app_encryption_key,
data.app_flags1,
data.app_reserved,
data.sig_name, data.sig_start,
data.sig_end, data.sig_target,
data.sig_signature_idx,
data.sig_signature_key,
data.sig_encryption_key,
data.sig_flags1,
data.sig_reserved
) = struct.unpack(data.STRUCT_STRUCTURE, bin)
return data
def parse_toc(bin):
'''
Searches for the TOC in the binary data.
This function looks for a TOC within certain boundaries in the TOC. It
also checks to find a valid TOC_end.
Throws exceptions, if a toc is not found, wrong version or end is not found.
bin: Binary data to look for the TOC
return: dataclass with the TOC entry
'''
# This is a fixed address from the linker file, TOC is placed after.
BOOT_DELAY_ADDR = 0x200
TOC_LEN_MAX = 64
EXPECTED_TOC_VERSION = 1
toc_start_len = struct.calcsize(TOC_start().STRUCT_STRUCTURE)
toc_entry_len = struct.calcsize(TOC_entry().STRUCT_STRUCTURE)
start_indx = bin.find(b'TOC', BOOT_DELAY_ADDR,
BOOT_DELAY_ADDR + TOC_LEN_MAX)
if start_indx <= 0:
raise Exception('TOC not found')
toc_start_header = bin2toc_start(
bin[start_indx:(start_indx+toc_start_len)])
print('TOC start found', toc_start_header,'@: ',hex(start_indx))
if toc_start_header.version != EXPECTED_TOC_VERSION:
raise Exception('Wrong TOC version!')
t = bin2toc_entry(
bin[start_indx+toc_start_len: (start_indx+toc_start_len+toc_entry_len)])
t.toc_position = start_indx+toc_start_len
print(t)
indx = bin.find(b'END', start_indx, start_indx + 512)
if indx <= 0:
toc_end = False
raise Exception('TOC end not found')
return t
def unpackPx4(file_path):
'''
Unpacks a .px4 file to get access to its binary data.
filepath: Path to a px4 file to extract.
return: A tuple of (binary data, json_data) of the file.
'''
# read the file
with open(file_path, "r") as f:
desc = json.load(f)
image = bytearray(zlib.decompress(base64.b64decode(desc['image'])))
return image, desc
def packPx4(bin_image, json_data, file_path, pub_key):
'''
Packs a new .px4 file with given binary data and information from jason file.
bin_image: New binary data with signature to add to .px4 file
json_data: The json data from the previously parsed .px4.
file_path: File path to then new .px4.sec image.
return: Nothing
'''
head,tail=os.path.splitext(file_path)
with open(head +'_signed.px4', 'w') as f:
json_data['signed'] = 'Hash512_Ed25519'
json_data['pub_key'] = pub_key.decode('utf-8')
json_data['image_size'] = len(bin_image)
json_data['image'] = base64.b64encode(
zlib.compress(bin_image, 9)).decode('utf-8')
print('Pack new signed.px4 file with signature')
json.dump(json_data, f, indent=4)
def ed25519_sign(private_key, signee_bin):
"""
This function creates the signature. It takes the private key and the binary file
and returns the tuple (signature, public key)
"""
signing_key = nacl.signing.SigningKey(
private_key, encoder=nacl.encoding.HexEncoder)
# Sign a message with the signing key
signed = signing_key.sign(signee_bin, encoder=nacl.encoding.RawEncoder)
# Obtain the verify key for a given signing key
verify_key = signing_key.verify_key
# Serialize the verify key to send it to a third party
verify_key_hex = verify_key.encode(encoder=nacl.encoding.HexEncoder)
return signed.signature, verify_key_hex
def write_toc(toc_old, bin, signature_name, new_pub_key_index, new_flags):
'''
Writes a new TOC entry.
toc_old: The parsed data_class with the data of the old TOC
bin: The binary to insert the new TOC
signature_name: 4 char name of the newly added signature
new_pub_key_index: Key index of the public key belongs to the signature
new_flags: New Toc flags from toc_flag_dict dictionary.
return: New bin with modified TOC
'''
toc_new = toc_old
toc_new.app_signature_key = new_pub_key_index
toc_new.app_flags1 = new_flags
if len(signature_name) != 4:
raise Exception('Signature name has not the right length')
toc_new.app_name = bytes(signature_name, 'utf_8')
toc_new_bin = toc2bin(toc_new)
toc_entry_len = struct.calcsize(TOC_entry().STRUCT_STRUCTURE)
bin[toc_old.toc_position:toc_old.toc_position+toc_entry_len] = toc_new_bin
return bin
def cli():
'''
Comand lined interface to the signtool.
See usage comand.
return: class with parsed arguments.
'''
parser = argparse.ArgumentParser(
description='Tool to extract and find crypto TOC from .px4 file. And append a signature from a given private key')
# defining arguments for parser object
parser.add_argument("--signee", type=str,
metavar="file_path", default=None,
help="Opens and reads the specified px4 file.")
parser.add_argument("--private_key", type=str,
metavar="key string", default=None,
help="Private key to sign the px4 image")
parser.add_argument("--key_index", type=int,
metavar="Number", default=None,
help="Index of the public key used to verify the binary")
parser.add_argument("--TOC_flags", type=str, choices=['TOC_FLAG1_BOOT', 'TOC_FLAG1_RDCT'],
default=None,
help="TOC flags to indicate signature")
args = parser.parse_args()
return args
def sign(file_path, private_key, key_index, TOC_flags):
'''
Signs a binary file and updates TOC accordingly.
Reads a .px4 or a .bin at specified location and writes a new _signed.[px4|bin] file at the same location.
file_path: Path to a .px4 or .bin file to sign.
private_key: String of the private key used to sign the binary.
key_index: Index of the public key used to verify signature.
TOC_flags: New toc flags to be written.
'''
head,tail=os.path.splitext(file_path)
if(tail == '.px4'):
bin, json_data = unpackPx4(file_path)
elif(tail == '.bin'):
with open(file_path,mode='rb') as f:
bin =bytearray(f.read())
else:
raise Exception('Error: Unknown file type')
toc_old = parse_toc(bin)
new_bin = write_toc(toc_old, bin, 'MSTR', key_index,
toc_flag_dict[TOC_flags])
app_len = toc_old.app_end - toc_old.app_start
print('Calculate new signature')
signature, pub_key = cryptotools.ed25519_sign(
private_key, bytes(new_bin[0:app_len]))
# Append signature to binary
print('Append signature to binary: ',signature.hex(),'\n','length: ',app_len )
sig_start=toc_old.sig_start - toc_old.app_start
sig_end = toc_old.sig_end - toc_old.app_start
if sig_start != app_len:
padding = bytearray(b'\xff')*(sig_start - app_len)
new_bin += padding
new_bin[toc_old.sig_start-toc_old.app_start:toc_old.sig_end -
toc_old.app_start] = signature
if(tail == '.px4'):
packPx4(new_bin, json_data, file_path,pub_key)
elif(tail == '.bin'):
head,tail=os.path.splitext(file_path)
with open(head +'_signed.bin',mode='wb') as f:
f.write(bin)
else:
raise Exception('Error: Unknown file type!')
if(__name__ == "__main__"):
args = cli()
# test input to the sign function if not run from CLI
# sign('PX4-Autopilot/build/px4_fmu-v5x_default/px4_fmu-v5x_default.px4',
# "8e969454c3f1ba924b4d436bc58b87d20c082ea99fb6a77b9e00f7b20dab0fd8",
# 0, 'TOC_FLAG1_BOOT')
# CLI test input:
# --signee /PX4-Autopilot/build/px4_fmu-v5x_default/px4_fmu-v5x_default.px4
# --private_key 8e969454c3f1ba924b4d436bc58b87d20c082ea99fb6a77b9e00f7b20dab0fd8 --key_index 0 --TOC_flags TOC_FLAG1_BOOT
sign(args.signee, args.private_key, args.key_index, args.TOC_flags)