#include #include #include #include #include "../config.h" #ifdef ENABLE_UPDATES #include "view.h" #include "dialogs.h" #include "http.h" #include "i18n.h" #include "sha1.h" #include "ecdsa.h" #include "update.h" #define UPDATE_HEADER "SIG0" #define UPDATE_OFFSET_R 4 #define UPDATE_OFFSET_S 4+30 #define UPDATE_OFFSET_XML 64 static const u8 update_key[] = UPDATE_PUBLIC_KEY; static u8 nibble2hex(u8 byte) { byte &= 0xf; if (byte <= 9) return '0' + byte; else return 'a' + byte - 0xa; } static void hash2ascii(u8 *hash, u8 *ascii) { u32 i; for (i = 0; i < 20; i++) { ascii[2*i] = nibble2hex(hash[i] >> 4); ascii[(2*i) + 1] = nibble2hex(hash[i]); } ascii[40] = 0; } typedef struct { u8 *sha_data; u32 sha_len; u8 *ecdsa_r; u8 *ecdsa_s; bool valid; bool running; } update_arg; static lwpq_t update_queue; static lwp_t update_thread; static u8 update_stack[UPDATE_THREAD_STACKSIZE] ATTRIBUTE_ALIGN (32); static update_arg ta_ua; static void *update_func(void *arg) { u8 hash[20]; update_arg *ua = (update_arg *) arg; SHA1(ua->sha_data, ua->sha_len, hash); if (check_ecdsa(update_key, ua->ecdsa_r, ua->ecdsa_s, hash)) ua->valid = true; ua->running = false; return NULL; } typedef enum { UPDATE_IDLE = 0, UPDATE_FETCH_META, UPDATE_VALIDATE, UPDATE_NO_UPDATE, UPDATE_CAN_UPDATE } update_state; static const char *update_url = UPDATE_URL; static update_state us = UPDATE_IDLE; static update_info *info = NULL; bool update_signal(void) { if ((us == UPDATE_FETCH_META) || us == UPDATE_CAN_UPDATE) return false; gprintf("starting update check\n"); if (http_request(update_url, 8 * 1024)) { us = UPDATE_FETCH_META; return true; } return false; } static u8 *update_data = NULL; static u32 update_len = 0; bool update_busy(bool *update_available) { http_res http_result; u32 status; s32 res; if ((us != UPDATE_FETCH_META) && (us != UPDATE_VALIDATE)) return false; if (us == UPDATE_FETCH_META) { if (!http_get_result(&http_result, &status, &update_data, &update_len)) return true; *update_available = false; us = UPDATE_NO_UPDATE; gprintf("update check done, status=%u len=%u\n", status, update_len); if (http_result != HTTPR_OK) return false; gprintf("fetched update xml\n"); if (update_len < 64) { gprintf("update.sxml length is invalid\n"); free(update_data); return false; } if (memcmp(UPDATE_HEADER, update_data, 4)) { gprintf("SIG0 header not found.\n"); free(update_data); return false; } memset(&ta_ua, 0, sizeof(ta_ua)); ta_ua.sha_data = update_data + UPDATE_OFFSET_XML; ta_ua.sha_len = update_len - UPDATE_OFFSET_XML; ta_ua.ecdsa_r = update_data + UPDATE_OFFSET_R; ta_ua.ecdsa_s = update_data + UPDATE_OFFSET_S; ta_ua.running = true; memset(&update_stack, 0, UPDATE_THREAD_STACKSIZE); LWP_InitQueue(&update_queue); res = LWP_CreateThread(&update_thread, update_func, &ta_ua, update_stack, UPDATE_THREAD_STACKSIZE, UPDATE_THREAD_PRIO); if (res) { gprintf("error creating thread: %d\n", res); LWP_CloseQueue(update_queue); free(update_data); return false; } us = UPDATE_VALIDATE; return true; } if (ta_ua.running) return true; LWP_CloseQueue(update_queue); us = UPDATE_NO_UPDATE; if (!ta_ua.valid) { gprintf("update.sxml signature is invalid.\n"); free(update_data); return false; } gprintf("update.sxml signature is valid.\n"); info = update_parse((char *) update_data + UPDATE_OFFSET_XML); free(update_data); if (!info) { gprintf("Failed to parse update.sxml\n"); return false; } if (!info->hash) { gprintf("no node in update.sxml\n"); update_free(info); info = NULL; return false; } if (strnlen(info->hash, 40) != 40) { gprintf("Invalid length\n"); update_free(info); info = NULL; return false; } if (info->date > CHANNEL_VERSION_DATE) { gprintf("update available\n"); us = UPDATE_CAN_UPDATE; *update_available = true; } else { gprintf("hbc is up2date\n"); update_free(info); info = NULL; } return false; } #define UPDATE_MSG_SIZE 4096 bool update_execute (view *sub_view, entry_point *ep) { http_state state; http_res res; u32 status, length, progress; u8 *data; view *v = sub_view; bool downloading = false; static char text[UPDATE_MSG_SIZE]; bool b = false; u8 hash[20]; u8 ascii_hash[41]; if (us != UPDATE_CAN_UPDATE) return false; us = UPDATE_IDLE; snprintf (text, UPDATE_MSG_SIZE, _("An update to the Homebrew Channel " "(version %s, replacing the installed version %s) " "is available for installation, do you want " "to update now?\n\n" "Release notes:\n\n%s"), info->version, CHANNEL_VERSION_STR, info->notes); text[UPDATE_MSG_SIZE-1] = 0; if (show_message (sub_view, DLGMT_CONFIRM, DLGB_YESNO, text, 0) == 1) { update_free(info); info = NULL; return false; } if (!http_request(info->uri, 4 * 1024 * 1024)) { gprintf ("error requesting update download\n"); update_free(info); info = NULL; return false; } while (true) { view_plot (v, DIALOG_MASK_COLOR, NULL, NULL, NULL); if (!http_get_state (&state, &length, &progress)) break; if ((!downloading) && (state == HTTPS_RECEIVING)) { downloading = true; snprintf (text, 32, _("Downloading Update (%u kB)"), length / 1024); v = dialog_progress (sub_view, text, length); dialog_fade (v, true); } if (downloading) dialog_set_progress (v, progress); if (http_get_result (&res, &status, &data, &length)) { gprintf("update download done, status=%u len=%u\n", status, length); if (res == HTTPR_OK) { SHA1(data, length, hash); hash2ascii(hash, ascii_hash); gprintf("Calculated hash : %s\n", ascii_hash); gprintf("Expected hash : %s\n", info->hash); if (memcmp(ascii_hash, info->hash, 40)) { gprintf("installer.elf hash is invalid.\n"); free(data); update_free(info); info = NULL; break; } gprintf("installer.elf hash is valid.\n"); b = true; } break; } } update_free(info); info = NULL; if (downloading) dialog_fade (v, false); if (!b) { show_message (sub_view, DLGMT_ERROR, DLGB_OK, _("Download failed"), 0); return false; } b = loader_reloc(ep, data, length, "installer.elf\0-updatehbc\0\0", 26, true); free (data); return b; } #endif