299 lines
8.4 KiB
C
299 lines
8.4 KiB
C
/*
|
|
* RabbitSign - Tools for signing TI graphing calculator software
|
|
* Copyright (C) 2009 Benjamin Moody
|
|
*
|
|
* 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 3 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/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#else
|
|
# ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
# endif
|
|
#endif
|
|
|
|
#include "rabbitsign.h"
|
|
#include "internal.h"
|
|
#include "md5.h"
|
|
|
|
/*
|
|
* Check/fix OS header fields and data.
|
|
*
|
|
* The OS header is much simpler than an application header, and its
|
|
* correctness is not as crucial to validation. The most important
|
|
* parts of the OS header are the key ID and (for newer calculators)
|
|
* the hardware compatibility level. There is no date stamp required.
|
|
* The page count is not required, and if present, is used only to
|
|
* display the transfer percentage (when using the 84+ boot code.)
|
|
*
|
|
* TI only sets the OS and program image size fields in their TI-73 OS
|
|
* headers. (Bizarrely, they are set in the true OS header, but not
|
|
* in the fake OS header that is transferred to page 1A. Furthermore,
|
|
* the OS size field is incorrect.) In any case, these fields appear
|
|
* to be ignored by all versions of the boot code.
|
|
*/
|
|
int rs_repair_ti8x_os(RSProgram* os, /* OS */
|
|
unsigned int flags) /* flags */
|
|
{
|
|
unsigned long hdrstart, hdrsize, fieldhead, fieldstart,
|
|
fieldsize, ossize;
|
|
unsigned char* hdr;
|
|
int i;
|
|
|
|
/* Pad the OS to a multiple of 16384. (While strictly speaking we
|
|
could get away with only padding each page to a multiple of 256,
|
|
such "partial OSes" are not supported by most linking
|
|
software.) */
|
|
|
|
rs_program_set_length(os, ((os->length + 0x3fff) & ~0x3fff));
|
|
|
|
/* If no OS header was provided in the input, try to get a header
|
|
from page 1A instead */
|
|
|
|
if (os->header_length < 6
|
|
|| os->header[0] != 0x80
|
|
|| os->header[1] != 0x0f) {
|
|
for (i = 0; i < os->npagenums; i++) {
|
|
if (os->pagenums[i] == 0x1a) {
|
|
rs_free(os->header);
|
|
if (!(os->header = rs_malloc(256)))
|
|
return RS_ERR_OUT_OF_MEMORY;
|
|
memcpy(os->header, os->data + ((unsigned long) i << 14), 256);
|
|
os->header_length = 256;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clear old header/signature (not done on the TI-73 because
|
|
official TI-73 OSes contain a fake header; I don't recommend
|
|
doing this for third-party OSes) */
|
|
|
|
if (os->calctype != RS_CALC_TI73)
|
|
for (i = 0; i < os->npagenums; i++)
|
|
if (os->pagenums[i] == 0x1a)
|
|
memset(os->data + ((unsigned long) i << 14), 0xff, 512);
|
|
|
|
/* Fix header size. OS headers must always begin with an 800x field
|
|
and end with an 807x field (TI always uses 800F and 807F, as for
|
|
apps; I'm not sure whether it's required.) */
|
|
|
|
if (os->header_length < 6
|
|
|| os->header[0] != 0x80
|
|
|| (os->header[1] & 0xf0) != 0) {
|
|
rs_error(NULL, os, "no OS header found");
|
|
return RS_ERR_MISSING_HEADER;
|
|
}
|
|
|
|
rs_get_field_size(os->header, &hdrstart, NULL);
|
|
hdr = os->header + hdrstart;
|
|
hdrsize = os->header_length - hdrstart;
|
|
|
|
if (rs_find_app_field(0x8070, hdr, hdrsize,
|
|
&fieldhead, &fieldstart, &fieldsize)) {
|
|
rs_error(NULL, os, "OS header has no program image field");
|
|
return RS_ERR_MISSING_PROGRAM_IMAGE;
|
|
}
|
|
|
|
hdrsize = fieldstart;
|
|
os->header_length = hdrstart + hdrsize;
|
|
|
|
if ((os->header_length % 64) == 55) {
|
|
if (flags & RS_IGNORE_ALL_WARNINGS) {
|
|
rs_warning(NULL, os, "OS header has length 55 mod 64");
|
|
rs_warning(NULL, os, "(this will fail to validate on TI-83+ BE)");
|
|
}
|
|
else {
|
|
rs_error(NULL, os, "OS header has length 55 mod 64");
|
|
rs_error(NULL, os, "(this will fail to validate on TI-83+ BE)");
|
|
return RS_ERR_INVALID_PROGRAM_SIZE;
|
|
}
|
|
}
|
|
|
|
/* Fix OS / OS image sizes if requested */
|
|
|
|
if (flags & RS_FIX_OS_SIZE) {
|
|
ossize = os->length + hdrsize;
|
|
if (rs_set_field_size(os->header, ossize)) {
|
|
rs_error(NULL, os, "cannot set OS length");
|
|
return RS_ERR_FIELD_TOO_SMALL;
|
|
}
|
|
|
|
if (rs_set_field_size(hdr + fieldhead, os->length)) {
|
|
rs_error(NULL, os, "cannot set OS image length");
|
|
return RS_ERR_FIELD_TOO_SMALL;
|
|
}
|
|
}
|
|
|
|
/* Check for key ID */
|
|
|
|
if (rs_find_app_field(0x8010, hdr, hdrsize, NULL, NULL, NULL)) {
|
|
if (flags & RS_IGNORE_ALL_WARNINGS)
|
|
rs_warning(NULL, os, "OS header has no key ID field");
|
|
else {
|
|
rs_error(NULL, os, "OS header has no key ID field");
|
|
return RS_ERR_MISSING_KEY_ID;
|
|
}
|
|
}
|
|
|
|
/* Check/fix page count */
|
|
|
|
if (rs_find_app_field(0x8080, hdr, hdrsize,
|
|
NULL, &fieldstart, &fieldsize)) {
|
|
if (os->length != 14 * 0x4000L) {
|
|
rs_warning(NULL, os, "OS header has no page count field");
|
|
}
|
|
}
|
|
else if (fieldsize != 1) {
|
|
rs_warning(NULL, os, "OS header has an invalid page count field");
|
|
}
|
|
else if (flags & RS_FIX_PAGE_COUNT) {
|
|
hdr[fieldstart] = os->length >> 14;
|
|
}
|
|
else if (hdr[fieldstart] != (os->length >> 14)) {
|
|
rs_warning(NULL, os, "OS header has an incorrect page count field");
|
|
}
|
|
|
|
/* Check and reset validation flag bytes */
|
|
|
|
if (os->data[0x56] != 0xff && os->data[0x56] != 0x5a) {
|
|
if (flags & RS_IGNORE_ALL_WARNINGS)
|
|
rs_warning(NULL, os, "OS has invalid data at 0056h");
|
|
else {
|
|
rs_error(NULL, os, "OS has invalid data at 0056h");
|
|
return RS_ERR_INVALID_PROGRAM_DATA;
|
|
}
|
|
}
|
|
|
|
if (os->data[0x56] == 0x5a)
|
|
os->data[0x56] = 0xff;
|
|
|
|
if (os->data[0x57] != 0xff && os->data[0x57] != 0xa5) {
|
|
if (flags & RS_IGNORE_ALL_WARNINGS)
|
|
rs_warning(NULL, os, "OS has invalid data at 0057h");
|
|
else {
|
|
rs_error(NULL, os, "OS has invalid data at 0057h");
|
|
return RS_ERR_INVALID_PROGRAM_DATA;
|
|
}
|
|
}
|
|
|
|
if (os->data[0x57] == 0xff)
|
|
os->data[0x57] = 0xa5;
|
|
|
|
return RS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Compute signature for an OS.
|
|
*/
|
|
int rs_sign_ti8x_os(RSProgram* os, /* OS */
|
|
RSKey* key) /* signing key */
|
|
{
|
|
struct md5_ctx ctx;
|
|
md5_uint32 hash[4];
|
|
mpz_t hashv, sigv;
|
|
unsigned char sigdata[512];
|
|
size_t siglength;
|
|
int e;
|
|
|
|
md5_init_ctx(&ctx);
|
|
md5_process_bytes(os->header, os->header_length, &ctx);
|
|
md5_process_bytes(os->data, os->length, &ctx);
|
|
md5_finish_ctx(&ctx, hash);
|
|
|
|
mpz_init(hashv);
|
|
mpz_init(sigv);
|
|
|
|
mpz_import(hashv, 16, -1, 1, 0, 0, hash);
|
|
rs_message(2, NULL, os, "hash = %ZX", hashv);
|
|
|
|
if ((e = rs_sign_rsa(sigv, hashv, key))) {
|
|
mpz_clear(hashv);
|
|
mpz_clear(sigv);
|
|
return e;
|
|
}
|
|
|
|
rs_message(2, NULL, os, "sig = %ZX", sigv);
|
|
|
|
sigdata[0] = 0x02;
|
|
sigdata[1] = 0x0d;
|
|
mpz_export(sigdata + 3, &siglength, -1, 1, 0, 0, sigv);
|
|
sigdata[2] = siglength & 0xff;
|
|
siglength += 3;
|
|
|
|
while (siglength < 96)
|
|
sigdata[siglength++] = 0xff;
|
|
|
|
rs_free(os->signature);
|
|
if (!(os->signature = rs_malloc(siglength)))
|
|
return RS_ERR_OUT_OF_MEMORY;
|
|
|
|
memcpy(os->signature, sigdata, siglength);
|
|
os->signature_length = siglength;
|
|
return RS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Validate OS signature.
|
|
*/
|
|
int rs_validate_ti8x_os(const RSProgram* os,
|
|
const RSKey* key)
|
|
{
|
|
unsigned long fieldstart, fieldsize;
|
|
struct md5_ctx ctx;
|
|
md5_uint32 hash[4];
|
|
mpz_t hashv, sigv;
|
|
int e;
|
|
|
|
if (os->signature_length < 3) {
|
|
rs_error(NULL, os, "OS does not have a signature");
|
|
return RS_ERR_MISSING_RSA_SIGNATURE;
|
|
}
|
|
|
|
if (os->signature[0] != 0x02 || (os->signature[1] & 0xf0) != 0x00) {
|
|
rs_error(NULL, os, "OS does not have an RSA signature");
|
|
return RS_ERR_MISSING_RSA_SIGNATURE;
|
|
}
|
|
rs_get_field_size(os->signature, &fieldstart, &fieldsize);
|
|
|
|
md5_init_ctx(&ctx);
|
|
md5_process_bytes(os->header, os->header_length, &ctx);
|
|
md5_process_bytes(os->data, os->length, &ctx);
|
|
md5_finish_ctx(&ctx, hash);
|
|
|
|
mpz_init(hashv);
|
|
mpz_init(sigv);
|
|
|
|
mpz_import(hashv, 16, -1, 1, 0, 0, hash);
|
|
rs_message(2, NULL, os, "hash = %ZX", hashv);
|
|
|
|
mpz_import(sigv, fieldsize, -1, 1, 0, 0, os->signature + fieldstart);
|
|
rs_message(2, NULL, os, "sig = %ZX", sigv);
|
|
|
|
e = rs_validate_rsa(sigv, hashv, key);
|
|
if (e == RS_SIGNATURE_INCORRECT)
|
|
rs_message(0, NULL, os, "OS signature incorrect");
|
|
|
|
mpz_clear(hashv);
|
|
mpz_clear(sigv);
|
|
return e;
|
|
}
|