/* * 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 . */ #ifdef HAVE_CONFIG_H # include #endif #include #ifdef HAVE_STRING_H # include #else # ifdef HAVE_STRINGS_H # include # 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; }