| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (C) 2016 Amaury Pouly |
| * |
| * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| #include "upg.h" |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include "md5.h" |
| |
| struct nwz_model_t g_model_list[] = |
| { |
| { "nwz-a10", true, "2572f4a7b8c1a08aeb5142ce9cb834d6" }, |
| { "nw-a20", true, "d91a61c7263bafc626e9a5b66f983c0b" }, |
| { "nwz-e450", true, "8a01b624bfbfde4a1662a1772220e3c5" }, |
| { "nwz-e460", true, "89d813f8f966efdebd9c9e0ea98156d2" }, |
| { "nwz-a860", true, "a7c4af6c28b8900a783f307c1ba538c5" }, |
| { "nwz-a850", true, "a2efb9168616c2e84d78291295c1aa5d" }, |
| { "nwz-a840", true, "78033fe79a67786fd79fbc138c865c68" }, |
| { "nwz-e470", true, "e4144baaa2707913f17b5634034262c4" }, |
| { "nwz-e580", true, "6e25f79812eca7ceed04819d833e80af" }, |
| { "nwz-s750", true, "6d4f4d9adec781baf197e6255cedd0f6" }, |
| { "nwz-x1000",true, "efafdee4c628fa7536de0b878cfe23af" }, |
| { "nw-zx100", true, "cdda8d5e5360fd4373154388743f84d2" }, |
| /* The following keys were obtained by brute forcing firmware upgrades, |
| * someone with a device needs to confirm that they work */ |
| { "nw-a820", false, "0c9869c268e0eaa6d1ba62daab09cebc" }, |
| /* The following models use a different encryption, but we put the KAS here |
| * to not forget them */ |
| { "nw-a30", false, "c40d91e7efff3e3aa5c8831dd85526fe4972086283419c8cd8fa3b7dcd39" }, |
| { "nw-wm1", false, "e8d171a5d92f35eed9658c03fb9f86a169591659851fd7c49525f587a70b" }, |
| { 0 } |
| }; |
| |
| static int digit_value(char c) |
| { |
| if(c >= '0' && c <= '9') return c - '0'; |
| if(c >= 'a' && c <= 'f') return c - 'a' + 10; |
| if(c >= 'A' && c <= 'F') return c - 'A' + 10; |
| return -1; |
| } |
| |
| static char hex_digit(unsigned v) |
| { |
| return (v < 10) ? v + '0' : (v < 16) ? v - 10 + 'a' : 'x'; |
| } |
| |
| int decrypt_keysig(const char kas[NWZ_KAS_SIZE], char key[NWZ_KEY_SIZE], |
| char sig[NWZ_SIG_SIZE]) |
| { |
| uint8_t src[NWZ_KAS_SIZE / 2]; |
| for(int index = 0; index < NWZ_KAS_SIZE / 2; index++) |
| { |
| int a = digit_value(kas[index * 2]); |
| int b = digit_value(kas[index * 2 + 1]); |
| if(a < 0 || b < 0) |
| return -1; |
| src[index] = a << 4 | b; |
| } |
| fwp_setkey("ed295076"); |
| fwp_crypt(src, sizeof(src), 1); |
| memcpy(key, src, NWZ_KEY_SIZE); |
| memcpy(sig, src + NWZ_KEY_SIZE, NWZ_SIG_SIZE); |
| return 0; |
| } |
| |
| void encrypt_keysig(char kas[NWZ_KEY_SIZE], |
| const char key[NWZ_SIG_SIZE], const char sig[NWZ_KAS_SIZE]) |
| { |
| uint8_t src[NWZ_KAS_SIZE / 2]; |
| fwp_setkey("ed295076"); |
| memcpy(src, key, NWZ_KEY_SIZE); |
| memcpy(src + NWZ_KEY_SIZE, sig, NWZ_SIG_SIZE); |
| fwp_crypt(src, sizeof(src), 0); |
| for(int i = 0; i < NWZ_KAS_SIZE / 2; i++) |
| { |
| kas[2 * i] = hex_digit((src[i] >> 4) & 0xf); |
| kas[2 * i + 1] = hex_digit(src[i] & 0xf); |
| } |
| } |
| |
| struct upg_file_t *upg_read_memory(void *buf, size_t size, char key[NWZ_KEY_SIZE], |
| char *sig, void *u, generic_printf_t printf) |
| { |
| #define cprintf(col, ...) printf(u, false, col, __VA_ARGS__) |
| #define cprintf_field(str1, ...) do{ cprintf(GREEN, str1); cprintf(YELLOW, __VA_ARGS__); }while(0) |
| #define err_printf(col, ...) printf(u, true, col, __VA_ARGS__) |
| struct upg_md5_t *md5 = buf; |
| cprintf(BLUE, "Preliminary\n"); |
| cprintf(GREEN, " MD5: "); |
| for(int i = 0; i < NWZ_MD5_SIZE; i++) |
| cprintf(YELLOW, "%02x", md5->md5[i]); |
| cprintf(OFF, " "); |
| |
| /* check MD5 */ |
| uint8_t actual_md5[NWZ_MD5_SIZE]; |
| MD5_CalculateDigest(actual_md5, (md5 + 1), size - sizeof(struct upg_header_t)); |
| if(memcmp(actual_md5, md5->md5, NWZ_MD5_SIZE) != 0) |
| { |
| cprintf(RED, "Mismatch\n"); |
| err_printf(GREY, "MD5 Mismatch\n"); |
| return NULL; |
| } |
| cprintf(RED, "Ok\n"); |
| |
| struct upg_header_t *hdr = (void *)(md5 + 1); |
| /* decrypt the whole file at once */ |
| fwp_read(hdr, size - sizeof(*md5), hdr, (void *)key); |
| |
| cprintf(BLUE, "Header\n"); |
| cprintf_field(" Signature:", " "); |
| for(int i = 0; i < NWZ_SIG_SIZE; i++) |
| cprintf(YELLOW, "%c", isprint(hdr->sig[i]) ? hdr->sig[i] : '.'); |
| if(sig) |
| { |
| if(memcmp(hdr->sig, sig, NWZ_SIG_SIZE) != 0) |
| { |
| cprintf(RED, "Mismatch\n"); |
| err_printf(GREY, "Signature Mismatch\n"); |
| return NULL; |
| } |
| cprintf(RED, " Ok\n"); |
| } |
| else |
| cprintf(RED, " Can't check\n"); |
| cprintf_field(" Files: ", "%d\n", hdr->nr_files); |
| cprintf_field(" Pad: ", "0x%x\n", hdr->pad); |
| |
| |
| /* Do a first pass to decrypt in-place */ |
| cprintf(BLUE, "Files\n"); |
| struct upg_entry_t *entry = (void *)(hdr + 1); |
| for(unsigned i = 0; i < hdr->nr_files; i++, entry++) |
| { |
| cprintf(GREY, " File"); |
| cprintf(RED, " %d\n", i); |
| cprintf_field(" Offset: ", "0x%x\n", entry->offset); |
| cprintf_field(" Size: ", "0x%x\n", entry->size); |
| } |
| /* Do a second pass to create the file structure */ |
| /* create file */ |
| struct upg_file_t *file = malloc(sizeof(struct upg_file_t)); |
| memset(file, 0, sizeof(struct upg_file_t)); |
| file->nr_files = hdr->nr_files; |
| file->files = malloc(sizeof(struct upg_file_entry_t) * file->nr_files); |
| |
| entry = (void *)(hdr + 1); |
| for(unsigned i = 0; i < hdr->nr_files; i++, entry++) |
| { |
| memset(&file->files[i], 0, sizeof(struct upg_file_entry_t)); |
| file->files[i].size = entry->size; |
| file->files[i].data = malloc(file->files[i].size); |
| memcpy(file->files[i].data, buf + entry->offset, entry->size); |
| } |
| |
| return file; |
| } |
| |
| void *upg_write_memory(struct upg_file_t *file, char key[NWZ_KEY_SIZE], |
| char sig[NWZ_SIG_SIZE], size_t *out_size, void *u, generic_printf_t printf) |
| { |
| if(file->nr_files == 0) |
| { |
| err_printf(GREY, "A UPG file must have at least one file\n"); |
| return NULL; |
| } |
| /* compute total size and create buffer */ |
| size_t tot_size = sizeof(struct upg_md5_t) + sizeof(struct upg_header_t) |
| + file->nr_files * sizeof(struct upg_entry_t); |
| for(int i = 0; i < file->nr_files; i++) |
| tot_size += ROUND_UP(file->files[i].size, 8); |
| /* allocate buffer */ |
| void *buf = malloc(tot_size); |
| |
| /* create md5 context, we push data to the context as we create it */ |
| struct upg_md5_t *md5 = buf; |
| memset(md5, 0, sizeof(*md5)); |
| /* create the encrypted signature and header */ |
| struct upg_header_t *hdr = (void *)(md5 + 1); |
| memcpy(hdr->sig, sig, NWZ_SIG_SIZE); |
| hdr->nr_files = file->nr_files; |
| hdr->pad = 0; |
| |
| /* create file headers */ |
| size_t offset = sizeof(*md5) + sizeof(*hdr) + file->nr_files * sizeof(struct upg_entry_t); |
| struct upg_entry_t *entry = (void *)(hdr + 1); |
| cprintf(BLUE, "Files\n"); |
| for(int i = 0; i < file->nr_files; i++) |
| { |
| entry[i].offset = offset; |
| entry[i].size = file->files[i].size; |
| offset += ROUND_UP(entry[i].size, 8); /* pad each file to a multiple of 8 for encryption */ |
| |
| cprintf(GREY, " File"); |
| cprintf(RED, " %d\n", i); |
| cprintf_field(" Offset: ", "0x%lx\n", entry[i].offset); |
| cprintf_field(" Size: ", "0x%lx\n", entry[i].size); |
| } |
| |
| /* add file data */ |
| for(int i = 0; i < file->nr_files; i++) |
| { |
| /* copy data to buffer, and then encrypt in-place */ |
| size_t r_size = ROUND_UP(file->files[i].size, 8); |
| void *data_ptr = (uint8_t *)buf + entry[i].offset; |
| memset(data_ptr, 0, r_size); /* the padding will be zero 0 */ |
| memcpy(data_ptr, file->files[i].data, file->files[i].size); |
| } |
| /* encrypt everything and hash everything */ |
| fwp_write(hdr, tot_size - sizeof(*md5), hdr, (void *)key); |
| /* write final MD5 */ |
| MD5_CalculateDigest(md5->md5, (void *)hdr, tot_size - sizeof(*md5)); |
| *out_size = tot_size; |
| return buf; |
| } |
| |
| struct upg_file_t *upg_new(void) |
| { |
| struct upg_file_t *f = malloc(sizeof(struct upg_file_t)); |
| memset(f, 0, sizeof(struct upg_file_t)); |
| return f; |
| } |
| |
| void upg_append(struct upg_file_t *file, void *data, size_t size) |
| { |
| file->files = realloc(file->files, (file->nr_files + 1) * sizeof(struct upg_file_entry_t)); |
| file->files[file->nr_files].data = data; |
| file->files[file->nr_files].size = size; |
| file->nr_files++; |
| } |
| |
| void upg_free(struct upg_file_t *file) |
| { |
| if(file) |
| { |
| for(int i = 0; i < file->nr_files; i++) |
| free(file->files[i].data); |
| free(file->files); |
| } |
| free(file); |
| } |