diff options
Diffstat (limited to 'src/ipsw.c')
-rw-r--r-- | src/ipsw.c | 235 |
1 files changed, 235 insertions, 0 deletions
@@ -23,6 +23,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <openssl/sha.h> #include "ipsw.h" #include "idevicerestore.h" @@ -219,3 +220,237 @@ void ipsw_close(ipsw_archive* archive) { free(archive); } } + +static int get_latest_fw(plist_t version_data, const char* product, char** fwurl, unsigned char* sha1buf) +{ + *fwurl = NULL; + memset(sha1buf, '\0', 20); + + plist_t n1 = plist_access_path(version_data, 2, "MobileDeviceMajorVersionsByProductType", product); + if (!n1 || (plist_dict_get_size(n1) == 0)) { + error("%s: ERROR: Can't find MobileDeviceMajorVersionsByProductType/%s dict in version data\n", __func__, product); + return -1; + } + + char* strval = NULL; + plist_t n2 = plist_dict_get_item(n1, "SameAs"); + if (n2) { + plist_get_string_val(n2, &strval); + } + if (strval) { + n1 = plist_access_path(version_data, 2, "MobileDeviceMajorVersionsByProductType", strval); + free(strval); + strval = NULL; + if (!n1 || (plist_dict_get_size(n1) == 0)) { + error("%s: ERROR: Can't find MobileDeviceMajorVersionsByProductType/%s dict in version data\n", __func__, product); + return -1; + } + } + + plist_dict_iter iter = NULL; + plist_dict_new_iter(n1, &iter); + if (!iter) { + error("%s: ERROR: Can't get dict iter\n", __func__); + return -1; + } + n2 = NULL; + char* key = NULL; + plist_t val = NULL; + do { + plist_dict_next_item(n1, iter, &key, &val); + if (key) { + n2 = val; + free(key); + } + } while (val); + free(iter); + + if (!n2) { + error("%s: ERROR: Can't get last node?!\n", __func__); + return -1; + } + + uint64_t major = 0; + if (plist_get_node_type(n2) == PLIST_ARRAY) { + uint32_t sz = plist_array_get_size(n2); + plist_t n3 = plist_array_get_item(n2, sz-1); + plist_get_uint_val(n3, &major); + } else { + plist_get_uint_val(n2, &major); + } + + if (major == 0) { + error("%s: ERROR: Can't find major version?!\n", __func__); + return -1; + } + + char majstr[32]; // should be enough for a uint64_t value + sprintf(majstr, FMT_qu, (long long unsigned int)major); + n1 = plist_access_path(version_data, 7, "MobileDeviceSoftwareVersionsByVersion", majstr, "MobileDeviceSoftwareVersions", product, "Unknown", "Universal", "Restore"); + if (!n1) { + error("%s: ERROR: Can't get Unknown/Universal/Restore node?!\n", __func__); + return -1; + } + + n2 = plist_dict_get_item(n1, "BuildVersion"); + if (!n2 || (plist_get_node_type(n2) != PLIST_STRING)) { + error("%s: ERROR: Can't get build version node?!\n", __func__); + return -1; + } + + strval = NULL; + plist_get_string_val(n2, &strval); + + n1 = plist_access_path(version_data, 5, "MobileDeviceSoftwareVersionsByVersion", majstr, "MobileDeviceSoftwareVersions", product, strval); + if (!n1) { + error("%s: ERROR: Can't get MobileDeviceSoftwareVersions/%s node?!\n", __func__, strval); + free(strval); + return -1; + } + free(strval); + + strval = NULL; + n2 = plist_dict_get_item(n1, "SameAs"); + if (n2) { + plist_get_string_val(n2, &strval); + } + if (strval) { + n1 = plist_access_path(version_data, 5, "MobileDeviceSoftwareVersionsByVersion", majstr, "MobileDeviceSoftwareVersions", product, strval); + free(strval); + strval = NULL; + if (!n1 || (plist_dict_get_size(n1) == 0)) { + error("%s: ERROR: Can't get MobileDeviceSoftwareVersions/%s dict\n", __func__, product); + return -1; + } + } + + n2 = plist_access_path(n1, 2, "Update", "BuildVersion"); + if (n2) { + strval = NULL; + plist_get_string_val(n2, &strval); + if (strval) { + n1 = plist_access_path(version_data, 5, "MobileDeviceSoftwareVersionsByVersion", majstr, "MobileDeviceSoftwareVersions", product, strval); + free(strval); + strval = NULL; + } + } + + n2 = plist_access_path(n1, 2, "Restore", "FirmwareURL"); + if (!n2 || (plist_get_node_type(n2) != PLIST_STRING)) { + error("%s: ERROR: Can't get FirmwareURL node\n", __func__); + return -1; + } + + plist_get_string_val(n2, fwurl); + + n2 = plist_access_path(n1, 2, "Restore", "FirmwareSHA1"); + if (n2 && plist_get_node_type(n2) == PLIST_STRING) { + strval = NULL; + plist_get_string_val(n2, &strval); + if (strval) { + if (strlen(strval) == 40) { + int i; + int v; + for (i = 0; i < 40; i+=2) { + v = 0; + sscanf(strval+i, "%02x", &v); + sha1buf[i/2] = (unsigned char)v; + } + } + free(strval); + } + } + + return 0; +} + +static int sha1_verify_fp(FILE* f, unsigned char* expected_sha1) +{ + unsigned char tsha1[20]; + char buf[8192]; + if (!f) return 0; + SHA_CTX sha1ctx; + SHA1_Init(&sha1ctx); + rewind(f); + while (!feof(f)) { + size_t sz = fread(buf, 1, 8192, f); + SHA1_Update(&sha1ctx, (const void*)buf, sz); + } + SHA1_Final(tsha1, &sha1ctx); + return (memcmp(expected_sha1, tsha1, 20) == 0) ? 1 : 0; +} + +int ipsw_download_latest_fw(plist_t version_data, const char* product, const char* todir, char** ipswfile) +{ + char* fwurl = NULL; + unsigned char isha1[20]; + + *ipswfile = NULL; + + if ((get_latest_fw(version_data, product, &fwurl, isha1) < 0) || !fwurl) { + error("ERROR: can't get URL for latest firmware\n"); + return -1; + } + char* fwfn = strrchr(fwurl, '/'); + if (!fwfn) { + error("ERROR: can't get local filename for firmware ipsw\n"); + return -1; + } + fwfn++; + + info("Latest firmware is %s\n", fwfn); + + char fwlfn[256]; + sprintf(fwlfn, "%s/%s", todir, fwfn); + + int need_dl = 0; + unsigned char zsha1[20] = {0, }; + FILE* f = fopen(fwlfn, "rb"); + if (f) { + if (memcmp(zsha1, isha1, 20) != 0) { + info("Verifying '%s'...\n", fwlfn); + if (sha1_verify_fp(f, isha1)) { + info("Checksum matches.\n"); + } else { + info("Checksum does not match.\n"); + need_dl = 1; + } + } + fclose(f); + } else { + need_dl = 1; + } + + int res = 0; + if (need_dl) { + if (strncmp(fwurl, "protected:", 10) == 0) { + error("ERROR: Can't download '%s' because it needs a purchase.\n", fwfn); + res = -1; + } else { + remove(fwlfn); + info("Downloading latest firmware (%s)\n", fwurl); + download_to_file(fwurl, fwlfn); + if (memcmp(isha1, zsha1, 20) != 0) { + info("\nVerifying '%s'...\n", fwlfn); + FILE* f = fopen(fwlfn, "rb"); + if (f) { + if (sha1_verify_fp(f, isha1)) { + info("Checksum matches.\n"); + } else { + error("ERROR: File download failed (checksum mismatch).\n"); + res = -1; + } + fclose(f); + } else { + error("ERROR: Can't open '%s' for checksum verification\n", fwlfn); + res = -1; + } + } + } + } + free(fwurl); + if (res == 0) { + *ipswfile = strdup(fwlfn); + } + return res; +} |