aboutsummaryrefslogtreecommitdiffstats
path: root/tools/firmware-utils/src/sign_dlink_ru.c
blob: 9c02ed50afcdec34ce68078e805065497973224f (plain)
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
/*
 * This program is designed to sign firmware images so they are accepted
 * by D-Link DIR-882 R1 WebUIs.
 *
 * Copyright (C) 2020 Andrew Pikler
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "md5.h"

#define BUF_SIZE 4096
#define MD5_HASH_LEN 16


typedef struct _md5_digest_t {
	uint8_t digest[MD5_HASH_LEN];
} md5_digest_t;

typedef struct _salt_t {
	char* salt_ascii;
	uint8_t* salt_bin;
	size_t salt_bin_len;
} salt_t;

void read_file_bytes(FILE* f, MD5_CTX* md5_ctx) {
	uint8_t buf[BUF_SIZE];
	size_t bytes_read;
	rewind(f);
	
	while (0 != (bytes_read = fread(buf, sizeof(uint8_t), BUF_SIZE, f))) {
		MD5_Update(md5_ctx, buf, bytes_read);
	}

	if (!feof(f)) {
		printf("Error: expected to be at EOF\n");
		exit(-1);
	}
}

void add_magic_bytes(FILE* f) {
	char magic_bytes[] = { 0x00, 0xc0, 0xff, 0xee };
	size_t magic_bytes_len = 4;
	fwrite(magic_bytes, magic_bytes_len, 1, f);
}

/**
 * Add the signature produced by this salt to the file
 * The signature consists by creating an MD5 digest wht the salt bytes plus
 * all of the bytes in the firmware file, then adding the magic bytes to the
 * file
 */
void add_signature(FILE* f, salt_t* salt) {
	md5_digest_t digest;
	MD5_CTX md5_context;

	MD5_Init(&md5_context);
	MD5_Update(&md5_context, salt->salt_bin, salt->salt_bin_len);
	read_file_bytes(f, &md5_context);
	MD5_Final(digest.digest, &md5_context);

	fwrite(&digest.digest, sizeof(uint8_t), MD5_HASH_LEN, f);
	add_magic_bytes(f);
}

void add_version_suffix(FILE* f) {
	char* version_suffix = "c0ffeef0rge";
	fseek(f, 0, SEEK_END);
	fwrite(version_suffix, sizeof(char), strlen(version_suffix), f);
}

int asciihex_to_int(char c) {
	if(c >= '0' && c <= 'F')
		return c - '0';

	if(c >= 'a' && c <= 'f')
		return 10 + c - 'a';
	return -1;
}

/**
 * Verify this is a valid hex string to convert
 */
void verify_valid_hex_str(char* s) {
	int i;
	int s_len = strlen(s);
	if (s_len == 0) {
		printf("invalid empty salt: %s\n", s);
		exit(-1);
	}

	if (s_len % 2 != 0) {
		printf("invalid odd len salt: %s\n", s);
		exit(-1);
	}

	for (i = 0; i < s_len; ++i) {
		if (asciihex_to_int(s[i]) < 0) {
			printf("invalid salt (invalid hex char): %s\n", s);
			exit(-1);
		}
	}
}

/**
 * Convert a hex ascii string to an allocated binary array. This array must be free'd
 */
uint8_t* convert_hex_to_bin(char * s) {
	int i;
	int s_len = strlen(s);

	uint8_t* ret = malloc(s_len / 2);
	for (i = 0; i < s_len; i += 2) {
		ret[i / 2] = (asciihex_to_int(s[i]) << 4) | asciihex_to_int(s[i + 1]);
	}

	return ret;
}

void init_salt(salt_t* salt, char * salt_ascii) {
	salt->salt_ascii = salt_ascii;
	salt->salt_bin = convert_hex_to_bin(salt_ascii);
	salt->salt_bin_len = strlen(salt_ascii) / 2;
}

void free_salt(salt_t* salt) {
	free(salt->salt_bin);
}

/**
 * Verify that the arguments are valid, or exit with failure
 */
void verify_args(int argc, char** argv) {
	int i;

	if (argc < 3) {
		printf("Usage: %s <firmware file> <signing hash1> <signing hash2> ... <signing hash n>\n", argv[0]);
		exit(1);
	}

	for (i = 2; i < argc; i++) {
		verify_valid_hex_str(argv[i]);
	}
}

FILE* make_out_file(char* filename) {
	uint8_t buf[BUF_SIZE];
	int bytes_read;
	char* suffix = ".new";
	int new_filename_len = strlen(filename) + strlen(suffix) + 1;
	char* new_filename = malloc(new_filename_len);
	strcpy(new_filename, filename);
	strcat(new_filename, suffix);

	FILE* f = fopen(filename, "r+");
	if (!f) {
		printf("cannot open file %s\n", filename);
		exit(2);
	}

	FILE* out = fopen(new_filename, "w+");
	free(new_filename);
	if (!out) {
		printf("cannot open file %s\n", filename);
		exit(2);
	}

	while (0 != (bytes_read = fread(buf, sizeof(uint8_t), BUF_SIZE, f))) {
		fwrite(buf, sizeof(uint8_t), bytes_read, out);
	}
	fclose(f);
	return out;
}

/**
 * Sign the firmware file after all of our checks have completed
 */
void sign_firmware(char* filename, char** salts, int num_salts) {
	int i;
	salt_t salt;
	FILE* f = make_out_file(filename);

	// add a version suffix string - dlink versions do something similar before the first signature
	add_version_suffix(f);

	//for each of the salts we are supplied with
	for (i = 0; i < num_salts; i++) {
		char* salt_str = salts[i];
		// convert this str to binary
		init_salt(&salt, salt_str);

		// add the signature to the firmware file produced from this salt
		add_signature(f, &salt);
		free_salt(&salt);
		printf("Signed with salt: %s\n", salt_str);
	}

	fclose(f);
}


int main(int argc, char ** argv) {
	verify_args(argc, argv);
	sign_firmware(argv[1], argv+2, argc-2);
	return 0;
}