rockbox/utils/nwztools/upgtools/upg.c
Amaury Pouly 92ecbd5fb8 nwztools/upg: move upg handling to its own file, completely rework kas handling
This was a huge mess, the new is much cleaner hopefully.

Change-Id: I43663d021dc8bc31662d3923e1c3da22d987ebf9
2017-01-04 17:04:38 +01:00

260 lines
8.7 KiB
C

/***************************************************************************
* __________ __ ___.
* 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 <openssl/md5.h>
struct nwz_model_t g_model_list[] =
{
{ "nwz-e450", true, "8a01b624bfbfde4a1662a1772220e3c5" },
{ "nwz-e460", true, "89d813f8f966efdebd9c9e0ea98156d2" },
{ "nwz-a860", true, "a7c4af6c28b8900a783f307c1ba538c5" },
{ "nwz-a850", true, "a2efb9168616c2e84d78291295c1aa5d" },
{ "nwz-e470", true, "e4144baaa2707913f17b5634034262c4" },
{ "nwz-e580", true, "6e25f79812eca7ceed04819d833e80af" },
/* The following keys were obtained by brute forcing firmware upgrades,
* someone with a device needs to confirm that they work */
{ "nw-a820", false, "0c9869c268e0eaa6d1ba62daab09cebc" },
{ "nwz-a10", false, "a4605e0628c9c3baeb5142ce9cb834d6" },
{ "nwz-a20", false, "e9d7185e5ac183bf26e9a5b66f983c0b" },
{ "nwz-zx100", false, "2c0bf029804f73e073154388743f84d2" },
{ 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 < MD5_DIGEST_LENGTH; i++)
cprintf(YELLOW, "%02x", md5->md5[i]);
cprintf(OFF, " ");
/* check MD5 */
uint8_t actual_md5[MD5_DIGEST_LENGTH];
{
MD5_CTX c;
MD5_Init(&c);
MD5_Update(&c, md5 + 1, size - sizeof(struct upg_header_t));
MD5_Final(actual_md5, &c);
}
if(memcmp(actual_md5, md5->md5, MD5_DIGEST_LENGTH) != 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_CTX c;
MD5_Init(&c);
MD5_Update(&c, (void *)hdr, tot_size - sizeof(*md5));
MD5_Final(md5->md5, &c);
}
*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);
}