1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * This program is designed to sign firmware images so they are accepted
4 * by D-Link DIR-882 R1 WebUIs.
6 * Copyright (C) 2020 Andrew Pikler
13 #include <sys/types.h>
20 #define MD5_HASH_LEN 16
23 typedef struct _md5_digest_t
{
24 uint8_t digest
[MD5_HASH_LEN
];
27 typedef struct _salt_t
{
33 void read_file_bytes(FILE* f
, MD5_CTX
* md5_ctx
) {
34 uint8_t buf
[BUF_SIZE
];
38 while (0 != (bytes_read
= fread(buf
, sizeof(uint8_t), BUF_SIZE
, f
))) {
39 MD5_Update(md5_ctx
, buf
, bytes_read
);
43 printf("Error: expected to be at EOF\n");
48 void add_magic_bytes(FILE* f
) {
49 char magic_bytes
[] = { 0x00, 0xc0, 0xff, 0xee };
50 size_t magic_bytes_len
= 4;
51 fwrite(magic_bytes
, magic_bytes_len
, 1, f
);
55 * Add the signature produced by this salt to the file
56 * The signature consists by creating an MD5 digest wht the salt bytes plus
57 * all of the bytes in the firmware file, then adding the magic bytes to the
60 void add_signature(FILE* f
, salt_t
* salt
) {
64 MD5_Init(&md5_context
);
65 MD5_Update(&md5_context
, salt
->salt_bin
, salt
->salt_bin_len
);
66 read_file_bytes(f
, &md5_context
);
67 MD5_Final(digest
.digest
, &md5_context
);
69 fwrite(&digest
.digest
, sizeof(uint8_t), MD5_HASH_LEN
, f
);
73 void add_version_suffix(FILE* f
) {
74 char* version_suffix
= "c0ffeef0rge";
75 fseek(f
, 0, SEEK_END
);
76 fwrite(version_suffix
, sizeof(char), strlen(version_suffix
), f
);
79 int asciihex_to_int(char c
) {
80 if(c
>= '0' && c
<= 'F')
83 if(c
>= 'a' && c
<= 'f')
89 * Verify this is a valid hex string to convert
91 void verify_valid_hex_str(char* s
) {
93 int s_len
= strlen(s
);
95 printf("invalid empty salt: %s\n", s
);
100 printf("invalid odd len salt: %s\n", s
);
104 for (i
= 0; i
< s_len
; ++i
) {
105 if (asciihex_to_int(s
[i
]) < 0) {
106 printf("invalid salt (invalid hex char): %s\n", s
);
113 * Convert a hex ascii string to an allocated binary array. This array must be free'd
115 uint8_t* convert_hex_to_bin(char * s
) {
117 int s_len
= strlen(s
);
119 uint8_t* ret
= malloc(s_len
/ 2);
120 for (i
= 0; i
< s_len
; i
+= 2) {
121 ret
[i
/ 2] = (asciihex_to_int(s
[i
]) << 4) | asciihex_to_int(s
[i
+ 1]);
127 void init_salt(salt_t
* salt
, char * salt_ascii
) {
128 salt
->salt_ascii
= salt_ascii
;
129 salt
->salt_bin
= convert_hex_to_bin(salt_ascii
);
130 salt
->salt_bin_len
= strlen(salt_ascii
) / 2;
133 void free_salt(salt_t
* salt
) {
134 free(salt
->salt_bin
);
138 * Verify that the arguments are valid, or exit with failure
140 void verify_args(int argc
, char** argv
) {
144 printf("Usage: %s <firmware file> <signing hash1> <signing hash2> ... <signing hash n>\n", argv
[0]);
148 for (i
= 2; i
< argc
; i
++) {
149 verify_valid_hex_str(argv
[i
]);
153 FILE* make_out_file(char* filename
) {
154 uint8_t buf
[BUF_SIZE
];
156 char* suffix
= ".new";
157 int new_filename_len
= strlen(filename
) + strlen(suffix
) + 1;
158 char* new_filename
= malloc(new_filename_len
);
159 strcpy(new_filename
, filename
);
160 strcat(new_filename
, suffix
);
162 FILE* f
= fopen(filename
, "r+");
164 printf("cannot open file %s\n", filename
);
168 FILE* out
= fopen(new_filename
, "w+");
171 printf("cannot open file %s\n", filename
);
175 while (0 != (bytes_read
= fread(buf
, sizeof(uint8_t), BUF_SIZE
, f
))) {
176 fwrite(buf
, sizeof(uint8_t), bytes_read
, out
);
183 * Sign the firmware file after all of our checks have completed
185 void sign_firmware(char* filename
, char** salts
, int num_salts
) {
188 FILE* f
= make_out_file(filename
);
190 // add a version suffix string - dlink versions do something similar before the first signature
191 add_version_suffix(f
);
193 //for each of the salts we are supplied with
194 for (i
= 0; i
< num_salts
; i
++) {
195 char* salt_str
= salts
[i
];
196 // convert this str to binary
197 init_salt(&salt
, salt_str
);
199 // add the signature to the firmware file produced from this salt
200 add_signature(f
, &salt
);
202 printf("Signed with salt: %s\n", salt_str
);
209 int main(int argc
, char ** argv
) {
210 verify_args(argc
, argv
);
211 sign_firmware(argv
[1], argv
+2, argc
-2);