buffalo-enc: pass the longstate option to decryption
[openwrt/staging/mkresin.git] / tools / firmware-utils / src / tplink-safeloader.c
1 /*
2 Copyright (c) 2014, Matthias Schiffer <mschiffer@universe-factory.net>
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 1. Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26
27 /*
28 tplink-safeloader
29
30 Image generation tool for the TP-LINK SafeLoader as seen on
31 TP-LINK Pharos devices (CPE210/220/510/520)
32 */
33
34
35 #include <assert.h>
36 #include <errno.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <stdint.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <time.h>
43 #include <unistd.h>
44
45 #include <arpa/inet.h>
46
47 #include <sys/types.h>
48 #include <sys/stat.h>
49
50 #include "md5.h"
51
52
53 #define ALIGN(x,a) ({ typeof(a) __a = (a); (((x) + __a - 1) & ~(__a - 1)); })
54
55
56 /** An image partition table entry */
57 struct image_partition_entry {
58 const char *name;
59 size_t size;
60 uint8_t *data;
61 };
62
63 /** A flash partition table entry */
64 struct flash_partition_entry {
65 const char *name;
66 uint32_t base;
67 uint32_t size;
68 };
69
70
71 /** The content of the soft-version structure */
72 struct __attribute__((__packed__)) soft_version {
73 uint32_t magic;
74 uint32_t zero;
75 uint8_t pad1;
76 uint8_t version_major;
77 uint8_t version_minor;
78 uint8_t version_patch;
79 uint8_t year_hi;
80 uint8_t year_lo;
81 uint8_t month;
82 uint8_t day;
83 uint32_t rev;
84 uint8_t pad2;
85 };
86
87
88 static const uint8_t jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
89
90
91 /**
92 Salt for the MD5 hash
93
94 Fortunately, TP-LINK seems to use the same salt for most devices which use
95 the new image format.
96 */
97 static const uint8_t md5_salt[16] = {
98 0x7a, 0x2b, 0x15, 0xed,
99 0x9b, 0x98, 0x59, 0x6d,
100 0xe5, 0x04, 0xab, 0x44,
101 0xac, 0x2a, 0x9f, 0x4e,
102 };
103
104
105 /** Vendor information for CPE210/220/510/520 */
106 static const unsigned char cpe510_vendor[] = "\x00\x00\x00\x1f""CPE510(TP-LINK|UN|N300-5):1.0\r\n";
107
108
109 /**
110 The flash partition table for CPE210/220/510/520;
111 it is the same as the one used by the stock images.
112 */
113 static const struct flash_partition_entry cpe510_partitions[] = {
114 {"fs-uboot", 0x00000, 0x20000},
115 {"partition-table", 0x20000, 0x02000},
116 {"default-mac", 0x30000, 0x00020},
117 {"product-info", 0x31100, 0x00100},
118 {"signature", 0x32000, 0x00400},
119 {"os-image", 0x40000, 0x170000},
120 {"soft-version", 0x1b0000, 0x00100},
121 {"support-list", 0x1b1000, 0x00400},
122 {"file-system", 0x1c0000, 0x600000},
123 {"user-config", 0x7c0000, 0x10000},
124 {"default-config", 0x7d0000, 0x10000},
125 {"log", 0x7e0000, 0x10000},
126 {"radio", 0x7f0000, 0x10000},
127 {NULL, 0, 0}
128 };
129
130 /**
131 The support list for CPE210/220/510/520
132
133 The stock images also contain strings for two more devices: BS510 and BS210.
134 At the moment, there exists no public information about these devices.
135 */
136 static const unsigned char cpe510_support_list[] =
137 "\x00\x00\x00\xc8\x00\x00\x00\x00"
138 "SupportList:\r\n"
139 "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
140 "CPE520(TP-LINK|UN|N300-5):1.0\r\n"
141 "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
142 "CPE220(TP-LINK|UN|N300-2):1.0\r\n"
143 "\r\n\xff";
144
145 #define error(_ret, _errno, _str, ...) \
146 do { \
147 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__, \
148 strerror(_errno)); \
149 if (_ret) \
150 exit(_ret); \
151 } while (0)
152
153
154 /** Allocates a new image partition */
155 struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
156 struct image_partition_entry entry = {name, len, malloc(len)};
157 if (!entry.data)
158 error(1, errno, "malloc");
159
160 return entry;
161 }
162
163 /** Frees an image partition */
164 void free_image_partition(struct image_partition_entry entry) {
165 free(entry.data);
166 }
167
168 /** Generates the partition-table partition */
169 struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
170 struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
171
172 char *s = (char *)entry.data, *end = (char *)(s+entry.size);
173
174 *(s++) = 0x00;
175 *(s++) = 0x04;
176 *(s++) = 0x00;
177 *(s++) = 0x00;
178
179 size_t i;
180 for (i = 0; p[i].name; i++) {
181 size_t len = end-s;
182 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
183
184 if (w > len-1)
185 error(1, 0, "flash partition table overflow?");
186
187 s += w;
188 }
189
190 s++;
191
192 memset(s, 0xff, end-s);
193
194 return entry;
195 }
196
197
198 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
199 static inline uint8_t bcd(uint8_t v) {
200 return 0x10 * (v/10) + v%10;
201 }
202
203
204 /** Generates the soft-version partition */
205 struct image_partition_entry make_soft_version(uint32_t rev) {
206 struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
207 struct soft_version *s = (struct soft_version *)entry.data;
208
209 time_t t;
210
211 if (time(&t) == (time_t)(-1))
212 error(1, errno, "time");
213
214 struct tm *tm = localtime(&t);
215
216 s->magic = htonl(0x0000000c);
217 s->zero = 0;
218 s->pad1 = 0xff;
219
220 s->version_major = 0;
221 s->version_minor = 0;
222 s->version_patch = 0;
223
224 s->year_hi = bcd((1900+tm->tm_year)/100);
225 s->year_lo = bcd(tm->tm_year%100);
226 s->month = bcd(tm->tm_mon+1);
227 s->day = bcd(tm->tm_mday);
228 s->rev = htonl(rev);
229
230 s->pad2 = 0xff;
231
232 return entry;
233 }
234
235 /** Generates the support-list partition */
236 struct image_partition_entry make_support_list(const unsigned char *support_list, size_t len) {
237 struct image_partition_entry entry = alloc_image_partition("support-list", len);
238 memcpy(entry.data, support_list, len);
239 return entry;
240 }
241
242 /** Creates a new image partition with an arbitrary name from a file */
243 struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
244 struct stat statbuf;
245
246 if (stat(filename, &statbuf) < 0)
247 error(1, errno, "unable to stat file `%s'", filename);
248
249 size_t len = statbuf.st_size;
250
251 if (add_jffs2_eof)
252 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
253
254 struct image_partition_entry entry = alloc_image_partition(part_name, len);
255
256 FILE *file = fopen(filename, "rb");
257 if (!file)
258 error(1, errno, "unable to open file `%s'", filename);
259
260 if (fread(entry.data, statbuf.st_size, 1, file) != 1)
261 error(1, errno, "unable to read file `%s'", filename);
262
263 if (add_jffs2_eof) {
264 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
265
266 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
267 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
268 }
269
270 fclose(file);
271
272 return entry;
273 }
274
275
276 /**
277 Copies a list of image partitions into an image buffer and generates the image partition table while doing so
278
279 Example image partition table:
280
281 fwup-ptn partition-table base 0x00800 size 0x00800
282 fwup-ptn os-image base 0x01000 size 0x113b45
283 fwup-ptn file-system base 0x114b45 size 0x1d0004
284 fwup-ptn support-list base 0x2e4b49 size 0x000d1
285
286 Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
287 the end of the partition table is marked with a zero byte.
288
289 The firmware image must contain at least the partition-table and support-list partitions
290 to be accepted. There aren't any alignment constraints for the image partitions.
291
292 The partition-table partition contains the actual flash layout; partitions
293 from the image partition table are mapped to the corresponding flash partitions during
294 the firmware upgrade. The support-list partition contains a list of devices supported by
295 the firmware image.
296
297 The base offsets in the firmware partition table are relative to the end
298 of the vendor information block, so the partition-table partition will
299 actually start at offset 0x1814 of the image.
300
301 I think partition-table must be the first partition in the firmware image.
302 */
303 void put_partitions(uint8_t *buffer, const struct image_partition_entry *parts) {
304 size_t i;
305 char *image_pt = (char *)buffer, *end = image_pt + 0x800;
306
307 size_t base = 0x800;
308 for (i = 0; parts[i].name; i++) {
309 memcpy(buffer + base, parts[i].data, parts[i].size);
310
311 size_t len = end-image_pt;
312 size_t w = snprintf(image_pt, len, "fwup-ptn %s base 0x%05x size 0x%05x\t\r\n", parts[i].name, (unsigned)base, (unsigned)parts[i].size);
313
314 if (w > len-1)
315 error(1, 0, "image partition table overflow?");
316
317 image_pt += w;
318
319 base += parts[i].size;
320 }
321
322 image_pt++;
323
324 memset(image_pt, 0xff, end-image_pt);
325 }
326
327 /** Generates and writes the image MD5 checksum */
328 void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
329 MD5_CTX ctx;
330
331 MD5_Init(&ctx);
332 MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
333 MD5_Update(&ctx, buffer, len);
334 MD5_Final(md5, &ctx);
335 }
336
337
338 /**
339 Generates the firmware image in factory format
340
341 Image format:
342
343 Bytes (hex) Usage
344 ----------- -----
345 0000-0003 Image size (4 bytes, big endian)
346 0004-0013 MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
347 0014-1013 Vendor information (4096 bytes, padded with 0xff; there seem to be older
348 (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
349 1014-1813 Image partition table (2048 bytes, padded with 0xff)
350 1814-xxxx Firmware partitions
351 */
352 void * generate_factory_image(const unsigned char *vendor, size_t vendor_len, const struct image_partition_entry *parts, size_t *len) {
353 *len = 0x1814;
354
355 size_t i;
356 for (i = 0; parts[i].name; i++)
357 *len += parts[i].size;
358
359 uint8_t *image = malloc(*len);
360 if (!image)
361 error(1, errno, "malloc");
362
363 image[0] = *len >> 24;
364 image[1] = *len >> 16;
365 image[2] = *len >> 8;
366 image[3] = *len;
367
368 memcpy(image+0x14, vendor, vendor_len);
369 memset(image+0x14+vendor_len, 0xff, 4096-vendor_len);
370
371 put_partitions(image + 0x1014, parts);
372 put_md5(image+0x04, image+0x14, *len-0x14);
373
374 return image;
375 }
376
377 /**
378 Generates the firmware image in sysupgrade format
379
380 This makes some assumptions about the provided flash and image partition tables and
381 should be generalized when TP-LINK starts building its safeloader into hardware with
382 different flash layouts.
383 */
384 void * generate_sysupgrade_image(const struct flash_partition_entry *flash_parts, const struct image_partition_entry *image_parts, size_t *len) {
385 const struct flash_partition_entry *flash_os_image = &flash_parts[5];
386 const struct flash_partition_entry *flash_soft_version = &flash_parts[6];
387 const struct flash_partition_entry *flash_support_list = &flash_parts[7];
388 const struct flash_partition_entry *flash_file_system = &flash_parts[8];
389
390 const struct image_partition_entry *image_os_image = &image_parts[3];
391 const struct image_partition_entry *image_soft_version = &image_parts[1];
392 const struct image_partition_entry *image_support_list = &image_parts[2];
393 const struct image_partition_entry *image_file_system = &image_parts[4];
394
395 assert(strcmp(flash_os_image->name, "os-image") == 0);
396 assert(strcmp(flash_soft_version->name, "soft-version") == 0);
397 assert(strcmp(flash_support_list->name, "support-list") == 0);
398 assert(strcmp(flash_file_system->name, "file-system") == 0);
399
400 assert(strcmp(image_os_image->name, "os-image") == 0);
401 assert(strcmp(image_soft_version->name, "soft-version") == 0);
402 assert(strcmp(image_support_list->name, "support-list") == 0);
403 assert(strcmp(image_file_system->name, "file-system") == 0);
404
405 if (image_os_image->size > flash_os_image->size)
406 error(1, 0, "kernel image too big (more than %u bytes)", (unsigned)flash_os_image->size);
407 if (image_file_system->size > flash_file_system->size)
408 error(1, 0, "rootfs image too big (more than %u bytes)", (unsigned)flash_file_system->size);
409
410 *len = flash_file_system->base - flash_os_image->base + image_file_system->size;
411
412 uint8_t *image = malloc(*len);
413 if (!image)
414 error(1, errno, "malloc");
415
416 memset(image, 0xff, *len);
417
418 memcpy(image, image_os_image->data, image_os_image->size);
419 memcpy(image + flash_soft_version->base - flash_os_image->base, image_soft_version->data, image_soft_version->size);
420 memcpy(image + flash_support_list->base - flash_os_image->base, image_support_list->data, image_support_list->size);
421 memcpy(image + flash_file_system->base - flash_os_image->base, image_file_system->data, image_file_system->size);
422
423 return image;
424 }
425
426
427 /** Generates an image for CPE210/220/510/520 and writes it to a file */
428 static void do_cpe510(const char *output, const char *kernel_image, const char *rootfs_image, uint32_t rev, bool add_jffs2_eof, bool sysupgrade) {
429 struct image_partition_entry parts[6] = {};
430
431 parts[0] = make_partition_table(cpe510_partitions);
432 parts[1] = make_soft_version(rev);
433 parts[2] = make_support_list(cpe510_support_list, sizeof(cpe510_support_list)-1);
434 parts[3] = read_file("os-image", kernel_image, false);
435 parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
436
437 size_t len;
438 void *image;
439 if (sysupgrade)
440 image = generate_sysupgrade_image(cpe510_partitions, parts, &len);
441 else
442 image = generate_factory_image(cpe510_vendor, sizeof(cpe510_vendor)-1, parts, &len);
443
444 FILE *file = fopen(output, "wb");
445 if (!file)
446 error(1, errno, "unable to open output file");
447
448 if (fwrite(image, len, 1, file) != 1)
449 error(1, 0, "unable to write output file");
450
451 fclose(file);
452
453 free(image);
454
455 size_t i;
456 for (i = 0; parts[i].name; i++)
457 free_image_partition(parts[i]);
458 }
459
460
461 /** Usage output */
462 void usage(const char *argv0) {
463 fprintf(stderr,
464 "Usage: %s [OPTIONS...]\n"
465 "\n"
466 "Options:\n"
467 " -B <board> create image for the board specified with <board>\n"
468 " -k <file> read kernel image from the file <file>\n"
469 " -r <file> read rootfs image from the file <file>\n"
470 " -o <file> write output to the file <file>\n"
471 " -V <rev> sets the revision number to <rev>\n"
472 " -j add jffs2 end-of-filesystem markers\n"
473 " -S create sysupgrade instead of factory image\n"
474 " -h show this help\n",
475 argv0
476 );
477 };
478
479
480 int main(int argc, char *argv[]) {
481 const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
482 bool add_jffs2_eof = false, sysupgrade = false;
483 unsigned rev = 0;
484
485 while (true) {
486 int c;
487
488 c = getopt(argc, argv, "B:k:r:o:V:jSh");
489 if (c == -1)
490 break;
491
492 switch (c) {
493 case 'B':
494 board = optarg;
495 break;
496
497 case 'k':
498 kernel_image = optarg;
499 break;
500
501 case 'r':
502 rootfs_image = optarg;
503 break;
504
505 case 'o':
506 output = optarg;
507 break;
508
509 case 'V':
510 sscanf(optarg, "r%u", &rev);
511 break;
512
513 case 'j':
514 add_jffs2_eof = true;
515 break;
516
517 case 'S':
518 sysupgrade = true;
519 break;
520
521 case 'h':
522 usage(argv[0]);
523 return 0;
524
525 default:
526 usage(argv[0]);
527 return 1;
528 }
529 }
530
531 if (!board)
532 error(1, 0, "no board has been specified");
533 if (!kernel_image)
534 error(1, 0, "no kernel image has been specified");
535 if (!rootfs_image)
536 error(1, 0, "no rootfs image has been specified");
537 if (!output)
538 error(1, 0, "no output filename has been specified");
539
540 if (strcmp(board, "CPE510") == 0)
541 do_cpe510(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade);
542 else
543 error(1, 0, "unsupported board %s", board);
544
545 return 0;
546 }