ti83-sdk/tool/rabbitsign-src/os8x.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;
}