firmware: add tool for signing d-link ru router factory firmware images
[openwrt/openwrt.git] / tools / firmware-utils / src / sign_dlink_ru.c
1 /*
2 * This program is designed to sign firmware images so they are accepted
3 * by D-Link DIR-882 R1 WebUIs.
4 *
5 * Copyright (C) 2020 Andrew Pikler
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <stdlib.h>
22 #include <stdint.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28
29 #include "md5.h"
30
31 #define BUF_SIZE 4096
32 #define MD5_HASH_LEN 16
33
34
35 typedef struct _md5_digest_t {
36 uint8_t digest[MD5_HASH_LEN];
37 } md5_digest_t;
38
39 typedef struct _salt_t {
40 char* salt_ascii;
41 uint8_t* salt_bin;
42 size_t salt_bin_len;
43 } salt_t;
44
45 void read_file_bytes(FILE* f, MD5_CTX* md5_ctx) {
46 uint8_t buf[BUF_SIZE];
47 size_t bytes_read;
48 rewind(f);
49
50 while (0 != (bytes_read = fread(buf, sizeof(uint8_t), BUF_SIZE, f))) {
51 MD5_Update(md5_ctx, buf, bytes_read);
52 }
53
54 if (!feof(f)) {
55 printf("Error: expected to be at EOF\n");
56 exit(-1);
57 }
58 }
59
60 void add_magic_bytes(FILE* f) {
61 char magic_bytes[] = { 0x00, 0xc0, 0xff, 0xee };
62 size_t magic_bytes_len = 4;
63 fwrite(magic_bytes, magic_bytes_len, 1, f);
64 }
65
66 /**
67 * Add the signature produced by this salt to the file
68 * The signature consists by creating an MD5 digest wht the salt bytes plus
69 * all of the bytes in the firmware file, then adding the magic bytes to the
70 * file
71 */
72 void add_signature(FILE* f, salt_t* salt) {
73 md5_digest_t digest;
74 MD5_CTX md5_context;
75
76 MD5_Init(&md5_context);
77 MD5_Update(&md5_context, salt->salt_bin, salt->salt_bin_len);
78 read_file_bytes(f, &md5_context);
79 MD5_Final(digest.digest, &md5_context);
80
81 fwrite(&digest.digest, sizeof(uint8_t), MD5_HASH_LEN, f);
82 add_magic_bytes(f);
83 }
84
85 void add_version_suffix(FILE* f) {
86 char* version_suffix = "c0ffeef0rge";
87 fseek(f, 0, SEEK_END);
88 fwrite(version_suffix, sizeof(char), strlen(version_suffix), f);
89 }
90
91 int asciihex_to_int(char c) {
92 if(c >= '0' && c <= 'F')
93 return c - '0';
94
95 if(c >= 'a' && c <= 'f')
96 return 10 + c - 'a';
97 return -1;
98 }
99
100 /**
101 * Verify this is a valid hex string to convert
102 */
103 void verify_valid_hex_str(char* s) {
104 int i;
105 int s_len = strlen(s);
106 if (s_len == 0) {
107 printf("invalid empty salt: %s\n", s);
108 exit(-1);
109 }
110
111 if (s_len % 2 != 0) {
112 printf("invalid odd len salt: %s\n", s);
113 exit(-1);
114 }
115
116 for (i = 0; i < s_len; ++i) {
117 if (asciihex_to_int(s[i]) < 0) {
118 printf("invalid salt (invalid hex char): %s\n", s);
119 exit(-1);
120 }
121 }
122 }
123
124 /**
125 * Convert a hex ascii string to an allocated binary array. This array must be free'd
126 */
127 uint8_t* convert_hex_to_bin(char * s) {
128 int i;
129 int s_len = strlen(s);
130
131 uint8_t* ret = malloc(s_len / 2);
132 for (i = 0; i < s_len; i += 2) {
133 ret[i / 2] = (asciihex_to_int(s[i]) << 4) | asciihex_to_int(s[i + 1]);
134 }
135
136 return ret;
137 }
138
139 void init_salt(salt_t* salt, char * salt_ascii) {
140 salt->salt_ascii = salt_ascii;
141 salt->salt_bin = convert_hex_to_bin(salt_ascii);
142 salt->salt_bin_len = strlen(salt_ascii) / 2;
143 }
144
145 void free_salt(salt_t* salt) {
146 free(salt->salt_bin);
147 }
148
149 /**
150 * Verify that the arguments are valid, or exit with failure
151 */
152 void verify_args(int argc, char** argv) {
153 int i;
154
155 if (argc < 3) {
156 printf("Usage: %s <firmware file> <signing hash1> <signing hash2> ... <signing hash n>\n", argv[0]);
157 exit(1);
158 }
159
160 for (i = 2; i < argc; i++) {
161 verify_valid_hex_str(argv[i]);
162 }
163 }
164
165 FILE* make_out_file(char* filename) {
166 uint8_t buf[BUF_SIZE];
167 int bytes_read;
168 char* suffix = ".new";
169 int new_filename_len = strlen(filename) + strlen(suffix) + 1;
170 char* new_filename = malloc(new_filename_len);
171 strcpy(new_filename, filename);
172 strcat(new_filename, suffix);
173
174 FILE* f = fopen(filename, "r+");
175 if (!f) {
176 printf("cannot open file %s\n", filename);
177 exit(2);
178 }
179
180 FILE* out = fopen(new_filename, "w+");
181 free(new_filename);
182 if (!out) {
183 printf("cannot open file %s\n", filename);
184 exit(2);
185 }
186
187 while (0 != (bytes_read = fread(buf, sizeof(uint8_t), BUF_SIZE, f))) {
188 fwrite(buf, sizeof(uint8_t), bytes_read, out);
189 }
190 fclose(f);
191 return out;
192 }
193
194 /**
195 * Sign the firmware file after all of our checks have completed
196 */
197 void sign_firmware(char* filename, char** salts, int num_salts) {
198 int i;
199 salt_t salt;
200 FILE* f = make_out_file(filename);
201
202 // add a version suffix string - dlink versions do something similar before the first signature
203 add_version_suffix(f);
204
205 //for each of the salts we are supplied with
206 for (i = 0; i < num_salts; i++) {
207 char* salt_str = salts[i];
208 // convert this str to binary
209 init_salt(&salt, salt_str);
210
211 // add the signature to the firmware file produced from this salt
212 add_signature(f, &salt);
213 free_salt(&salt);
214 printf("Signed with salt: %s\n", salt_str);
215 }
216
217 fclose(f);
218 }
219
220
221 int main(int argc, char ** argv) {
222 verify_args(argc, argv);
223 sign_firmware(argv[1], argv+2, argc-2);
224 return 0;
225 }