diff --git a/source/cert.c b/source/cert.c index 1da30aa..cf28111 100644 --- a/source/cert.c +++ b/source/cert.c @@ -22,15 +22,26 @@ #include "save.h" #include "utils.h" -#define CERT_SAVEFILE_PATH "sys:/save/80000000000000e0" +#define CERT_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e0" #define CERT_SAVEFILE_STORAGE_BASE_PATH "/certificate/" #define CERT_TYPE(sig) (pub_key_type == CertPubKeyType_Rsa4096 ? CertType_Sig##sig##_PubKeyRsa4096 : (pub_key_type == CertPubKeyType_Rsa2048 ? CertType_Sig##sig##_PubKeyRsa2048 : CertType_Sig##sig##_PubKeyEcsda240)) +/* Global variables. */ + +static save_ctx_t *g_esCertSaveCtx = NULL; + /* Function prototypes. */ +static bool certOpenEsCertSaveFile(void); +static void certCloseEsCertSaveFile(void); + +static bool _certRetrieveCertificateByName(Certificate *dst, const char *name); static u8 certGetCertificateType(const void *data, u64 data_size); + +static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer); static u32 certGetCertificateCountInSignatureIssuer(const char *issuer); + static u64 certCalculateRawCertificateChainSize(const CertificateChain *chain); static void certCopyCertificateChainDataToMemoryBuffer(void *dst, const CertificateChain *chain); @@ -42,66 +53,13 @@ bool certRetrieveCertificateByName(Certificate *dst, const char *name) return false; } - save_ctx_t *save_ctx = NULL; - allocation_table_storage_ctx_t fat_storage = {0}; + if (!certOpenEsCertSaveFile()) return false; - u64 cert_size = 0; - char cert_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = {0}; + bool ret = _certRetrieveCertificateByName(dst, name); - bool success = false; + certCloseEsCertSaveFile(); - snprintf(cert_path, SAVE_FS_LIST_MAX_NAME_LENGTH, "%s%s", CERT_SAVEFILE_STORAGE_BASE_PATH, name); - - save_ctx = save_open_savefile(CERT_SAVEFILE_PATH, 0); - if (!save_ctx) - { - LOGFILE("Failed to open ES certificate system savefile!"); - return false; - } - - if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, cert_path, &fat_storage, &cert_size)) - { - LOGFILE("Failed to locate certificate \"%s\" in ES certificate system save!", name); - goto out; - } - - if (cert_size < CERT_MIN_SIZE || cert_size > CERT_MAX_SIZE) - { - LOGFILE("Invalid size for certificate \"%s\"! (0x%lX)", name, cert_size); - goto out; - } - - dst->size = cert_size; - - u64 br = save_allocation_table_storage_read(&fat_storage, dst->data, 0, dst->size); - if (br != dst->size) - { - LOGFILE("Failed to read 0x%lX bytes from certificate \"%s\"! Read 0x%lX bytes.", dst->size, name, br); - goto out; - } - - dst->type = certGetCertificateType(dst->data, dst->size); - if (dst->type == CertType_Invalid) - { - LOGFILE("Invalid certificate type for \"%s\"!", name); - goto out; - } - - success = true; - -out: - if (save_ctx) save_close_savefile(save_ctx); - - return success; -} - -void certFreeCertificateChain(CertificateChain *chain) -{ - if (!chain || !chain->certs) return; - - chain->count = 0; - free(chain->certs); - chain->certs = NULL; + return ret; } bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer) @@ -112,45 +70,22 @@ bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const return false; } - u32 i = 0; - char issuer_copy[0x40] = {0}; - bool success = true; + if (!certOpenEsCertSaveFile()) return false; - dst->count = certGetCertificateCountInSignatureIssuer(issuer); - if (!dst->count) - { - LOGFILE("Invalid signature issuer string!"); - return false; - } + bool ret = _certRetrieveCertificateChainBySignatureIssuer(dst, issuer); - dst->certs = calloc(dst->count, sizeof(Certificate)); - if (!dst->certs) - { - LOGFILE("Unable to allocate memory for the certificate chain! (0x%lX)", dst->count * sizeof(Certificate)); - return false; - } + certCloseEsCertSaveFile(); - /* Copy string to avoid problems with strtok */ - /* The "Root-" parent from the issuer string is skipped */ - snprintf(issuer_copy, 0x40, issuer + 5); + return ret; +} + +void certFreeCertificateChain(CertificateChain *chain) +{ + if (!chain || !chain->certs) return; - char *pch = strtok(issuer_copy, "-"); - while(pch != NULL) - { - if (!certRetrieveCertificateByName(&(dst->certs[i]), pch)) - { - LOGFILE("Unable to retrieve certificate \"%s\"!", pch); - success = false; - break; - } - - i++; - pch = strtok(NULL, "-"); - } - - if (!success) certFreeCertificateChain(dst); - - return success; + chain->count = 0; + free(chain->certs); + chain->certs = NULL; } u8 *certGenerateRawCertificateChainBySignatureIssuer(const char *issuer, u64 *out_size) @@ -189,6 +124,74 @@ out: return raw_chain; } +static bool certOpenEsCertSaveFile(void) +{ + if (g_esCertSaveCtx) return true; + + g_esCertSaveCtx = save_open_savefile(CERT_SAVEFILE_PATH, 0); + if (!g_esCertSaveCtx) + { + LOGFILE("Failed to open ES certificate system savefile!"); + return false; + } + + return true; +} + +static void certCloseEsCertSaveFile(void) +{ + if (g_esCertSaveCtx) + { + save_close_savefile(g_esCertSaveCtx); + g_esCertSaveCtx = NULL; + } +} + +static bool _certRetrieveCertificateByName(Certificate *dst, const char *name) +{ + if (!g_esCertSaveCtx || !dst || !name || !strlen(name)) + { + LOGFILE("Invalid parameters!"); + return false; + } + + u64 cert_size = 0; + char cert_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = {0}; + allocation_table_storage_ctx_t fat_storage = {0}; + + snprintf(cert_path, SAVE_FS_LIST_MAX_NAME_LENGTH, "%s%s", CERT_SAVEFILE_STORAGE_BASE_PATH, name); + + if (!save_get_fat_storage_from_file_entry_by_path(g_esCertSaveCtx, cert_path, &fat_storage, &cert_size)) + { + LOGFILE("Failed to locate certificate \"%s\" in ES certificate system save!", name); + return false; + } + + if (cert_size < CERT_MIN_SIZE || cert_size > CERT_MAX_SIZE) + { + LOGFILE("Invalid size for certificate \"%s\"! (0x%lX)", name, cert_size); + return false; + } + + dst->size = cert_size; + + u64 br = save_allocation_table_storage_read(&fat_storage, dst->data, 0, dst->size); + if (br != dst->size) + { + LOGFILE("Failed to read 0x%lX bytes from certificate \"%s\"! Read 0x%lX bytes.", dst->size, name, br); + return false; + } + + dst->type = certGetCertificateType(dst->data, dst->size); + if (dst->type == CertType_Invalid) + { + LOGFILE("Invalid certificate type for \"%s\"!", name); + return false; + } + + return true; +} + static u8 certGetCertificateType(const void *data, u64 data_size) { if (!data || data_size < CERT_MIN_SIZE || data_size > CERT_MAX_SIZE) @@ -271,6 +274,55 @@ static u8 certGetCertificateType(const void *data, u64 data_size) return type; } +static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer) +{ + if (!dst || !issuer || !strlen(issuer) || strncmp(issuer, "Root-", 5) != 0) + { + LOGFILE("Invalid parameters!"); + return false; + } + + u32 i = 0; + char issuer_copy[0x40] = {0}; + bool success = true; + + dst->count = certGetCertificateCountInSignatureIssuer(issuer); + if (!dst->count) + { + LOGFILE("Invalid signature issuer string!"); + return false; + } + + dst->certs = calloc(dst->count, sizeof(Certificate)); + if (!dst->certs) + { + LOGFILE("Unable to allocate memory for the certificate chain! (0x%lX)", dst->count * sizeof(Certificate)); + return false; + } + + /* Copy string to avoid problems with strtok */ + /* The "Root-" parent from the issuer string is skipped */ + snprintf(issuer_copy, 0x40, issuer + 5); + + char *pch = strtok(issuer_copy, "-"); + while(pch != NULL) + { + if (!_certRetrieveCertificateByName(&(dst->certs[i]), pch)) + { + LOGFILE("Unable to retrieve certificate \"%s\"!", pch); + success = false; + break; + } + + i++; + pch = strtok(NULL, "-"); + } + + if (!success) certFreeCertificateChain(dst); + + return success; +} + static u32 certGetCertificateCountInSignatureIssuer(const char *issuer) { if (!issuer || !strlen(issuer)) return 0; diff --git a/source/cert.h b/source/cert.h index a495260..1e12386 100644 --- a/source/cert.h +++ b/source/cert.h @@ -158,8 +158,8 @@ typedef struct { bool certRetrieveCertificateByName(Certificate *dst, const char *name); -void certFreeCertificateChain(CertificateChain *chain); bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer); +void certFreeCertificateChain(CertificateChain *chain); /// Returns a pointer to a heap allocated buffer that must be freed by the user. u8 *certGenerateRawCertificateChainBySignatureIssuer(const char *issuer, u64 *out_size); diff --git a/source/es.c b/source/es.c index 748c2a8..b87e09a 100644 --- a/source/es.c +++ b/source/es.c @@ -57,7 +57,7 @@ Result esListCommonTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 cou Result rc = serviceDispatchInOut(&g_esSrv, 11, *out_entries_written, out, .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, - .buffers = { { out_ids, count * sizeof(FsRightsId) } } + .buffers = { { out_ids, (size_t)count * sizeof(FsRightsId) } } ); if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written; @@ -73,7 +73,7 @@ Result esListPersonalizedTicket(s32 *out_entries_written, FsRightsId *out_ids, s Result rc = serviceDispatchInOut(&g_esSrv, 12, *out_entries_written, out, .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, - .buffers = { { out_ids, count * sizeof(FsRightsId) } } + .buffers = { { out_ids, (size_t)count * sizeof(FsRightsId) } } ); if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written; diff --git a/source/fatfs/diskio.c b/source/fatfs/diskio.c index a10270e..2effa66 100644 --- a/source/fatfs/diskio.c +++ b/source/fatfs/diskio.c @@ -61,10 +61,7 @@ DRESULT disk_read ( u64 start_offset = ((u64)FF_MAX_SS * (u64)sector); u64 read_size = ((u64)FF_MAX_SS * (u64)count); - FsStorage *emmc_storage = utilsGetEmmcBisSystemStorage(); - if (!emmc_storage) return RES_ERROR; - - rc = fsStorageRead(emmc_storage, start_offset, buff, read_size); + rc = fsStorageRead(utilsGetEmmcBisSystemPartitionStorage(), start_offset, buff, read_size); return (R_SUCCEEDED(rc) ? RES_OK : RES_ERROR); } diff --git a/source/gamecard.c b/source/gamecard.c index 70de8fa..ba22022 100644 --- a/source/gamecard.c +++ b/source/gamecard.c @@ -41,6 +41,9 @@ #define GAMECARD_CAPACITY_16GiB (u64)0x400000000 #define GAMECARD_CAPACITY_32GiB (u64)0x800000000 +#define GAMECARD_HFS_PARTITION_NAME(x) ((x) == GameCardHashFileSystemPartitionType_Update ? "update" : ((x) == GameCardHashFileSystemPartitionType_Logo ? "logo" : \ + ((x) == GameCardHashFileSystemPartitionType_Normal ? "normal" : ((x) == GameCardHashFileSystemPartitionType_Secure ? "secure" : "unknown")))) + /* Type definitions. */ typedef enum { @@ -95,13 +98,13 @@ static bool gamecardGetHandle(void); static inline void gamecardCloseHandle(void); static bool gamecardOpenStorageArea(u8 area); -static bool gamecardReadStorageArea(void *out, u64 out_size, u64 offset, bool lock); +static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool lock); static void gamecardCloseStorageArea(void); static bool gamecardGetStorageAreasSizes(void); static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size); -static inline GameCardHashFileSystemHeader *gamecardGetHashFileSystemPartitionHeaderByIndex(u32 idx); +static bool gamecardGetHashFileSystemPartitionIndexByType(u8 type, u32 *out); static inline GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *hfs_header, u32 idx); static inline char *gamecardGetHashFileSystemEntryName(void *hfs_header, u32 name_offset); @@ -120,9 +123,9 @@ bool gamecardIsReady(void) return ret; } -bool gamecardRead(void *out, u64 out_size, u64 offset) +bool gamecardRead(void *out, u64 read_size, u64 offset) { - return gamecardReadStorageArea(out, out_size, offset, true); + return gamecardReadStorageArea(out, read_size, offset, true); } bool gamecardGetHeader(GameCardHeader *out) @@ -222,30 +225,25 @@ bool gamecardGetBundledFirmwareUpdateVersion(u32 *out) return ret; } -bool gamecardGetHashFileSystemEntryDataOffsetByName(u32 hfs_partition_idx, const char *name, u64 *out_offset) +bool gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(u8 hfs_partition_type, const char *name, u64 *out_offset, u64 *out_size) { bool ret = false; char *entry_name = NULL; size_t name_len = 0; + u32 hfs_partition_idx = 0; GameCardHashFileSystemHeader *fs_header = NULL; GameCardHashFileSystemEntry *fs_entry = NULL; mtx_lock(&g_gameCardSharedDataMutex); - if (!g_gameCardInserted || !g_gameCardInfoLoaded || !name || !*name || !out_offset) + if (!g_gameCardInserted || !g_gameCardInfoLoaded || !name || !*name || !out_offset || !out_size || !gamecardGetHashFileSystemPartitionIndexByType(hfs_partition_type, &hfs_partition_idx)) { LOGFILE("Invalid parameters!"); goto out; } name_len = strlen(name); - - fs_header = gamecardGetHashFileSystemPartitionHeaderByIndex(hfs_partition_idx); - if (!fs_header) - { - LOGFILE("Invalid hash FS partition index! (0x%X)", hfs_partition_idx); - goto out; - } + fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsPartitions[hfs_partition_idx].header; for(u32 i = 0; i < fs_header->entry_count; i++) { @@ -258,6 +256,7 @@ bool gamecardGetHashFileSystemEntryDataOffsetByName(u32 hfs_partition_idx, const if (!strncasecmp(entry_name, name, name_len)) { *out_offset = (g_gameCardHfsPartitions[hfs_partition_idx].offset + g_gameCardHfsPartitions[hfs_partition_idx].header_size + fs_entry->offset); + *out_size = fs_entry->size; ret = true; break; } @@ -447,7 +446,7 @@ static int gamecardDetectionThreadFunc(void *arg) if (!prev_status && g_gameCardInserted) { /* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules */ - SLEEP(GAMECARD_ACCESS_WAIT_TIME); + utilsSleep(GAMECARD_ACCESS_WAIT_TIME); /* Load gamecard info */ gamecardLoadInfo(); @@ -738,14 +737,14 @@ static bool gamecardOpenStorageArea(u8 area) return true; } -static bool gamecardReadStorageArea(void *out, u64 out_size, u64 offset, bool lock) +static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool lock) { if (lock) mtx_lock(&g_gameCardSharedDataMutex); bool success = false; - if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !out_size || \ - offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || (offset + out_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize)) + if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || \ + offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || (offset + read_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize)) { LOGFILE("Invalid parameters!"); goto out; @@ -756,7 +755,7 @@ static bool gamecardReadStorageArea(void *out, u64 out_size, u64 offset, bool lo u8 area = (offset < g_gameCardStorageNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure); /* Handle reads that span both the normal and secure gamecard storage areas */ - if (area == GameCardStorageArea_Normal && (offset + out_size) > g_gameCardStorageNormalAreaSize) + if (area == GameCardStorageArea_Normal && (offset + read_size) > g_gameCardStorageNormalAreaSize) { /* Calculate normal storage area size difference */ u64 diff_size = (g_gameCardStorageNormalAreaSize - offset); @@ -764,7 +763,7 @@ static bool gamecardReadStorageArea(void *out, u64 out_size, u64 offset, bool lo if (!gamecardReadStorageArea(out_u8, diff_size, offset, false)) goto out; /* Adjust variables to read right from the start of the secure storage area */ - out_size -= diff_size; + read_size -= diff_size; offset = g_gameCardStorageNormalAreaSize; out_u8 += diff_size; area = GameCardStorageArea_Secure; @@ -781,13 +780,13 @@ static bool gamecardReadStorageArea(void *out, u64 out_size, u64 offset, bool lo /* Calculate appropiate storage area offset and retrieve the right storage area pointer */ u64 base_offset = (area == GameCardStorageArea_Normal ? offset : (offset - g_gameCardStorageNormalAreaSize)); - if (!(base_offset % GAMECARD_MEDIA_UNIT_SIZE) && !(out_size % GAMECARD_MEDIA_UNIT_SIZE)) + if (!(base_offset % GAMECARD_MEDIA_UNIT_SIZE) && !(read_size % GAMECARD_MEDIA_UNIT_SIZE)) { /* Optimization for reads that are already aligned to GAMECARD_MEDIA_UNIT_SIZE bytes */ - rc = fsStorageRead(&g_gameCardStorage, base_offset, out_u8, out_size); + rc = fsStorageRead(&g_gameCardStorage, base_offset, out_u8, read_size); if (R_FAILED(rc)) { - LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (aligned)", out_size, base_offset, GAMECARD_STORAGE_AREA_NAME(area), rc); + LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (aligned)", read_size, base_offset, GAMECARD_STORAGE_AREA_NAME(area), rc); goto out; } @@ -795,11 +794,11 @@ static bool gamecardReadStorageArea(void *out, u64 out_size, u64 offset, bool lo } else { /* Fix offset and/or size to avoid unaligned reads */ u64 block_start_offset = (base_offset - (base_offset % GAMECARD_MEDIA_UNIT_SIZE)); - u64 block_end_offset = ROUND_UP(base_offset + out_size, GAMECARD_MEDIA_UNIT_SIZE); + u64 block_end_offset = ROUND_UP(base_offset + read_size, GAMECARD_MEDIA_UNIT_SIZE); u64 block_size = (block_end_offset - block_start_offset); u64 chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? GAMECARD_READ_BUFFER_SIZE : block_size); - u64 out_chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? (GAMECARD_READ_BUFFER_SIZE - (base_offset - block_start_offset)) : out_size); + u64 out_chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? (GAMECARD_READ_BUFFER_SIZE - (base_offset - block_start_offset)) : read_size); rc = fsStorageRead(&g_gameCardStorage, block_start_offset, g_gameCardReadBuf, chunk_size); if (R_FAILED(rc)) @@ -810,7 +809,7 @@ static bool gamecardReadStorageArea(void *out, u64 out_size, u64 offset, bool lo memcpy(out_u8, g_gameCardReadBuf + (base_offset - block_start_offset), out_chunk_size); - success = (block_size > GAMECARD_READ_BUFFER_SIZE ? gamecardReadStorageArea(out_u8 + out_chunk_size, out_size - out_chunk_size, base_offset + out_chunk_size, false) : true); + success = (block_size > GAMECARD_READ_BUFFER_SIZE ? gamecardReadStorageArea(out_u8 + out_chunk_size, read_size - out_chunk_size, base_offset + out_chunk_size, false) : true); } out: @@ -905,10 +904,30 @@ static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size) return capacity; } -static inline GameCardHashFileSystemHeader *gamecardGetHashFileSystemPartitionHeaderByIndex(u32 idx) +static bool gamecardGetHashFileSystemPartitionIndexByType(u8 type, u32 *out) { - if (idx >= ((GameCardHashFileSystemHeader*)g_gameCardHfsRootHeader)->entry_count) return NULL; - return (GameCardHashFileSystemHeader*)g_gameCardHfsPartitions[idx].header; + if (type > GameCardHashFileSystemPartitionType_Secure || !out) return false; + + char *entry_name = NULL; + GameCardHashFileSystemEntry *fs_entry = NULL; + GameCardHashFileSystemHeader *fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsRootHeader; + + for(u32 i = 0; i < fs_header->entry_count; i++) + { + fs_entry = gamecardGetHashFileSystemEntryByIndex(fs_header, i); + if (!fs_entry) continue; + + entry_name = gamecardGetHashFileSystemEntryName(fs_header, fs_entry->name_offset); + if (!entry_name) continue; + + if (!strcasecmp(entry_name, GAMECARD_HFS_PARTITION_NAME(type))) + { + *out = i; + return true; + } + } + + return false; } static inline GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *hfs_header, u32 idx) diff --git a/source/gamecard.h b/source/gamecard.h index 9f5d933..5c8ebfe 100644 --- a/source/gamecard.h +++ b/source/gamecard.h @@ -131,6 +131,13 @@ typedef struct { u8 hash[SHA256_HASH_SIZE]; } GameCardHashFileSystemEntry; +typedef enum { + GameCardHashFileSystemPartitionType_Update = 0, + GameCardHashFileSystemPartitionType_Logo = 1, ///< Only available in GameCardFwVersion_Since400NUP gamecards. + GameCardHashFileSystemPartitionType_Normal = 2, + GameCardHashFileSystemPartitionType_Secure = 3 +} GameCardHashFileSystemPartitionType; + /// Initializes data needed to access raw gamecard storage areas. /// Also spans a background thread to automatically detect gamecard status changes and to cache data from the inserted gamecard. Result gamecardInitialize(void); @@ -144,8 +151,8 @@ bool gamecardIsReady(void); /// Used to read data from the inserted gamecard. /// All required handles, changes between normal <-> secure storage areas and proper offset calculations are managed internally. -/// offset + out_size should never exceed the value returned by gamecardGetTotalSize(). -bool gamecardRead(void *out, u64 out_size, u64 offset); +/// offset + read_size should never exceed the value returned by gamecardGetTotalSize(). +bool gamecardRead(void *out, u64 read_size, u64 offset); /// Miscellaneous functions. bool gamecardGetHeader(GameCardHeader *out); @@ -155,6 +162,6 @@ bool gamecardGetRomCapacity(u64 *out); ///< Not the same as gamecardGetTotalSize bool gamecardGetCertificate(FsGameCardCertificate *out); bool gamecardGetBundledFirmwareUpdateVersion(u32 *out); -bool gamecardGetHashFileSystemEntryDataOffsetByName(u32 hfs_partition_idx, const char *name, u64 *out_offset); +bool gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(u8 hfs_partition_type, const char *name, u64 *out_offset, u64 *out_size); ///< GameCardHashFileSystemPartitionType. #endif /* __GAMECARD_H__ */ diff --git a/source/keys.c b/source/keys.c index bf0c37a..eb1b03a 100644 --- a/source/keys.c +++ b/source/keys.c @@ -65,7 +65,7 @@ typedef struct { ///< Needed to decrypt the titlekey block from a ticker. Retrieved from the Lockpick_RCM keys file. u8 eticket_rsa_kek[0x10]; ///< eTicket RSA kek. - u8 titlekeks[0x20][0x10]; ///< Title key encryption keys. + u8 titlekeks[0x20][0x10]; ///< Titlekey encryption keys. ///< Needed to reencrypt the NCA key area for tik-less NSP dumps. Retrieved from the Lockpick_RCM keys file. u8 key_area_keys[0x20][3][0x10]; ///< Key area encryption keys. diff --git a/source/main.c b/source/main.c index 5ee8684..7b67a1f 100644 --- a/source/main.c +++ b/source/main.c @@ -21,7 +21,10 @@ //#include "lvgl_helper.h" #include "utils.h" + #include "gamecard.h" +#include "tik.h" +#include "cert.h" int main(int argc, char *argv[]) { @@ -65,7 +68,7 @@ int main(int argc, char *argv[]) FsGameCardCertificate cert = {0}; u64 total_size = 0, trimmed_size = 0; u32 update_version = 0; - u64 nca_offset = 0; + u64 nca_offset = 0, nca_size = 0; if (gamecardGetHeader(&header)) { @@ -108,7 +111,7 @@ int main(int argc, char *argv[]) if (gamecardGetCertificate(&cert)) { - printf("cert success\n"); + printf("gamecard cert success\n"); consoleUpdate(NULL); tmp_file = fopen("sdmc:/cert.bin", "wb"); @@ -117,12 +120,12 @@ int main(int argc, char *argv[]) fwrite(&cert, 1, sizeof(FsGameCardCertificate), tmp_file); fclose(tmp_file); tmp_file = NULL; - printf("cert saved\n"); + printf("gamecard cert saved\n"); } else { - printf("cert not saved\n"); + printf("gamecard cert not saved\n"); } } else { - printf("cert failed\n"); + printf("gamecard cert failed\n"); } consoleUpdate(NULL); @@ -168,16 +171,77 @@ int main(int argc, char *argv[]) consoleUpdate(NULL); - if (gamecardGetHashFileSystemEntryDataOffsetByName(2, "7e86768383cfabb30f1b58d2373fed07.nca", &nca_offset)) // Should match 0x1657F5E00 + // Should match 0x1657F5E00 + if (gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(GameCardHashFileSystemPartitionType_Secure, "7e86768383cfabb30f1b58d2373fed07.nca", &nca_offset, &nca_size)) { - printf("nca_offset: 0x%lX\n", nca_offset); + printf("nca_offset: 0x%lX | nca_size: 0x%lX\n", nca_offset, nca_size); } else { - printf("nca_offset failed\n"); + printf("nca_offset + nca_size failed\n"); } consoleUpdate(NULL); - SLEEP(3); + Ticket tik = {0}; + TikCommonBlock *tik_common_blk = NULL; + + u8 *cert_chain = NULL; + u64 cert_chain_size = 0; + + FsRightsId rights_id = { + .c = { 0x01, 0x00, 0x9a, 0xa0, 0x00, 0xfa, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } // Sonic Mania + }; + + if (tikRetrieveTicketByRightsId(&tik, &rights_id, false)) + { + printf("tik succeeded\n"); + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/tik.bin", "wb"); + if (tmp_file) + { + fwrite(&tik, 1, sizeof(Ticket), tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("tik saved\n"); + } else { + printf("tik not saved\n"); + } + + consoleUpdate(NULL); + + tik_common_blk = tikGetCommonBlockFromTicket(&tik); + + if (tik_common_blk) + { + cert_chain = certGenerateRawCertificateChainBySignatureIssuer(tik_common_blk->issuer, &cert_chain_size); + if (cert_chain) + { + printf("cert chain succeeded | size: 0x%lX\n", cert_chain_size); + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/chain.bin", "wb"); + if (tmp_file) + { + fwrite(cert_chain, 1, cert_chain_size, tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("cert chain saved\n"); + } else { + printf("cert chain not saved\n"); + } + } else { + printf("cert chain failed\n"); + } + } + } else { + printf("tik failed\n"); + } + + consoleUpdate(NULL); + + + + utilsSleep(5); consoleExit(NULL); out: diff --git a/source/save.c b/source/save.c index 2c9ffbe..e9ad1a2 100644 --- a/source/save.c +++ b/source/save.c @@ -124,7 +124,7 @@ static remap_segment_ctx_t *save_remap_init_segments(remap_header_t *header, rem return NULL; } - remap_segment_ctx_t *segments = calloc(sizeof(remap_segment_ctx_t), header->map_segment_count); + remap_segment_ctx_t *segments = calloc(header->map_segment_count, sizeof(remap_segment_ctx_t)); if (!segments) { LOGFILE("Failed to allocate initial memory for remap segments!"); @@ -1598,10 +1598,7 @@ void save_free_contexts(save_ctx_t *ctx) { for(unsigned int i = 0; i < ctx->data_remap_storage.header->map_segment_count; i++) { - for(unsigned int j = 0; j < ctx->data_remap_storage.segments[i].entry_count; j++) - { - if (ctx->data_remap_storage.segments[i].entries[j]) free(ctx->data_remap_storage.segments[i].entries[j]); - } + if (ctx->data_remap_storage.segments[i].entries) free(ctx->data_remap_storage.segments[i].entries); } } @@ -1621,10 +1618,7 @@ void save_free_contexts(save_ctx_t *ctx) { for(unsigned int i = 0; i < ctx->meta_remap_storage.header->map_segment_count; i++) { - for(unsigned int j = 0; j < ctx->meta_remap_storage.segments[i].entry_count; j++) - { - if (ctx->meta_remap_storage.segments[i].entries[j]) free(ctx->meta_remap_storage.segments[i].entries[j]); - } + if (ctx->meta_remap_storage.segments[i].entries) free(ctx->meta_remap_storage.segments[i].entries); } } @@ -1732,15 +1726,22 @@ save_ctx_t *save_open_savefile(const char *path, u32 action) } FRESULT fr = FR_OK; - FIL save_fd = {0}; + FIL *save_fd = NULL; save_ctx_t *save_ctx = NULL; bool open_savefile = false, success = false; - fr = f_open(&save_fd, path, FA_READ | FA_OPEN_EXISTING); + save_fd = calloc(1, sizeof(FIL)); + if (!save_fd) + { + LOGFILE("Unable to allocate memory for FatFs file descriptor!"); + return NULL; + } + + fr = f_open(save_fd, path, FA_READ | FA_OPEN_EXISTING); if (fr != FR_OK) { LOGFILE("Failed to open \"%s\" savefile from BIS System partition! (%u)", path, fr); - return NULL; + goto out; } open_savefile = true; @@ -1748,11 +1749,11 @@ save_ctx_t *save_open_savefile(const char *path, u32 action) save_ctx = calloc(1, sizeof(save_ctx_t)); if (!save_ctx) { - LOGFILE("Failed to allocate memory for savefile \"%s\" context!", path); + LOGFILE("Unable to allocate memory for savefile \"%s\" context!", path); goto out; } - save_ctx->file = &save_fd; + save_ctx->file = save_fd; save_ctx->tool_ctx.action = action; success = save_process(save_ctx); @@ -1767,7 +1768,11 @@ out: save_ctx = NULL; } - if (open_savefile) f_close(&save_fd); + if (save_fd) + { + if (open_savefile) f_close(save_fd); + free(save_fd); + } } return save_ctx; @@ -1777,7 +1782,11 @@ void save_close_savefile(save_ctx_t *ctx) { if (!ctx) return; - if (ctx->file) f_close(ctx->file); + if (ctx->file) + { + f_close(ctx->file); + free(ctx->file); + } save_free_contexts(ctx); diff --git a/source/tik.c b/source/tik.c index eec3dc7..40d4045 100644 --- a/source/tik.c +++ b/source/tik.c @@ -23,10 +23,11 @@ #include "es.h" #include "keys.h" #include "rsa.h" +#include "gamecard.h" #include "utils.h" -#define TIK_COMMON_SAVEFILE_PATH "sys:/save/80000000000000e1" -#define TIK_PERSONALIZED_SAVEFILE_PATH "sys:/save/80000000000000e2" +#define TIK_COMMON_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e1" +#define TIK_PERSONALIZED_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e2" #define TIK_SAVEFILE_STORAGE_PATH "/ticket.bin" #define ETICKET_DEVKEY_PUBLIC_EXPONENT 0x10001 @@ -57,49 +58,160 @@ static const u8 g_nullHash[0x20] = { /* Function prototypes. */ -static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized); -static u8 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id); -static bool tikGetTicketTypeAndSize(const void *data, u64 data_size, u8 *out_type, u64 *out_size); -static bool tikTestKeyPairFromEticketDeviceKey(const void *e, const void *d, const void *n); -static bool tikRetrieveEticketDeviceKey(void); +static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsId *id); +static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRightsId *id); -TikCommonBlock *tikGetTicketCommonBlockFromMemoryBuffer(void *data) +static TikCommonBlock *tikGetCommonBlockFromMemoryBuffer(void *data); + +static bool tikGetTitleKeyFromTicketCommonBlock(void *dst, const TikCommonBlock *tik_common_blk); +static bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_generation); + +static u8 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id); +static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized); + +static bool tikGetTicketTypeAndSize(const void *data, u64 data_size, u8 *out_type, u64 *out_size); + +static bool tikRetrieveEticketDeviceKey(void); +static bool tikTestKeyPairFromEticketDeviceKey(const void *e, const void *d, const void *n); + +bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gamecard) { - if (!data) + bool tik_retrieved = false; + TikCommonBlock *tik_common_blk = NULL; + + tik_retrieved = (use_gamecard ? tikRetrieveTicketFromGameCardByRightsId(dst, id) : tikRetrieveTicketFromEsSaveDataByRightsId(dst, id)); + if (!tik_retrieved) + { + LOGFILE("Unable to retrieve ticket data!"); + return false; + } + + tik_common_blk = tikGetCommonBlockFromMemoryBuffer(dst->data); + if (!tik_common_blk) + { + LOGFILE("Unable to retrieve common block from ticket!"); + return false; + } + + if (!tikGetTitleKeyFromTicketCommonBlock(dst->enc_titlekey, tik_common_blk)) + { + LOGFILE("Unable to retrieve titlekey from ticket!"); + return false; + } + + /* Even though tickets do have a proper key_generation field, we'll just retrieve it from the rights_id field */ + /* Old custom tools used to wipe the key_generation field or save it to a different offset */ + if (!tikGetTitleKekDecryptedTitleKey(dst->dec_titlekey, dst->enc_titlekey, tik_common_blk->rights_id.c[0xF])) + { + LOGFILE("Unable to perform titlekek decryption!"); + return false; + } + + return true; +} + +TikCommonBlock *tikGetCommonBlockFromTicket(Ticket *tik) +{ + if (!tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE) { LOGFILE("Invalid parameters!"); return NULL; } - u8 *data_u8 = (u8*)data; - TikCommonBlock *tik_common_blk = NULL; - u32 sig_type; - - memcpy(&sig_type, data_u8, sizeof(u32)); - - switch(sig_type) - { - case SignatureType_Rsa4096Sha1: - case SignatureType_Rsa4096Sha256: - tik_common_blk = (TikCommonBlock*)(data_u8 + sizeof(SignatureBlockRsa4096)); - break; - case SignatureType_Rsa2048Sha1: - case SignatureType_Rsa2048Sha256: - tik_common_blk = (TikCommonBlock*)(data_u8 + sizeof(SignatureBlockRsa2048)); - break; - case SignatureType_Ecsda240Sha1: - case SignatureType_Ecsda240Sha256: - tik_common_blk = (TikCommonBlock*)(data_u8 + sizeof(SignatureBlockEcsda240)); - break; - default: - LOGFILE("Invalid signature type value! (0x%08X)", sig_type); - return NULL; - } - - return tik_common_blk; + return tikGetCommonBlockFromMemoryBuffer(tik->data); } -bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id) +void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik) +{ + if (!tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE) return; + + bool dev_cert = false; + TikCommonBlock *tik_common_blk = NULL; + + tik_common_blk = tikGetCommonBlockFromMemoryBuffer(tik->data); + if (!tik_common_blk || tik_common_blk->titlekey_type != TikTitleKeyType_Personalized) return; + + switch(tik->type) + { + case TikType_SigRsa4096: + tik->size = sizeof(TikSigRsa4096); + memset(tik->data + 4, 0xFF, MEMBER_SIZE(SignatureBlockRsa4096, signature)); + break; + case TikType_SigRsa2048: + tik->size = sizeof(TikSigRsa2048); + memset(tik->data + 4, 0xFF, MEMBER_SIZE(SignatureBlockRsa2048, signature)); + break; + case TikType_SigEcsda240: + tik->size = sizeof(TikSigEcsda240); + memset(tik->data + 4, 0xFF, MEMBER_SIZE(SignatureBlockEcsda240, signature)); + break; + default: + break; + } + + dev_cert = (strstr(tik_common_blk->issuer, "CA00000004") != NULL); + + memset(tik_common_blk->issuer, 0, sizeof(tik_common_blk->issuer)); + sprintf(tik_common_blk->issuer, "Root-CA%08X-XS00000020", dev_cert ? 4 : 3); + + memset(tik_common_blk->titlekey_block, 0, sizeof(tik_common_blk->titlekey_block)); + memcpy(tik_common_blk->titlekey_block, tik->enc_titlekey, 0x10); + + tik_common_blk->titlekey_type = TikTitleKeyType_Common; + tik_common_blk->ticket_id = 0; + tik_common_blk->device_id = 0; + tik_common_blk->account_id = 0; + + tik_common_blk->sect_total_size = 0; + tik_common_blk->sect_hdr_offset = (u32)tik->size; + tik_common_blk->sect_hdr_count = 0; + tik_common_blk->sect_hdr_entry_size = 0; + + memset(tik->data + tik->size, 0, TIK_MAX_SIZE - tik->size); +} + +static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsId *id) +{ + if (!dst || !id) + { + LOGFILE("Invalid parameters!"); + return false; + } + + char tik_filename[0x30] = {0}; + u64 tik_offset = 0, tik_size = 0; + + utilsGenerateHexStringFromData(tik_filename, sizeof(tik_filename), id->c, 0x10); + strcat(tik_filename, ".tik"); + + if (!gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(GameCardHashFileSystemPartitionType_Secure, tik_filename, &tik_offset, &tik_size)) + { + LOGFILE("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!"); + return false; + } + + if (tik_size < TIK_MIN_SIZE || tik_size > TIK_MAX_SIZE) + { + LOGFILE("Invalid size for \"%s\"! (0x%lX)", tik_filename, tik_size); + return false; + } + + if (!gamecardRead(dst->data, tik_size, tik_offset)) + { + LOGFILE("Failed to read \"%s\" data from the inserted gamecard!", tik_filename); + return false; + } + + if (!tikGetTicketTypeAndSize(dst->data, tik_size, &(dst->type), &(dst->size))) + { + LOGFILE("Unable to determine ticket type and size!"); + return false; + } + + return true; +} + +static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRightsId *id) { if (!dst || !id) { @@ -114,28 +226,28 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id) u64 ticket_bin_size = 0; u64 buf_size = (TIK_MAX_SIZE * 0x10); /* 0x4000 */ - u64 br = buf_size, total_br = 0; + u64 br = 0, total_br = 0; u8 *ticket_bin_buf = NULL; bool found_tik = false, success = false; - u8 title_key_type = tikGetTitleKeyTypeFromRightsId(id); - if (title_key_type == TikTitleKeyType_Invalid) + u8 titlekey_type = tikGetTitleKeyTypeFromRightsId(id); + if (titlekey_type == TikTitleKeyType_Invalid) { LOGFILE("Unable to retrieve ticket titlekey type!"); return false; } - save_ctx = save_open_savefile(title_key_type == TikTitleKeyType_Common ? TIK_COMMON_SAVEFILE_PATH : TIK_PERSONALIZED_SAVEFILE_PATH, 0); + save_ctx = save_open_savefile(titlekey_type == TikTitleKeyType_Common ? TIK_COMMON_SAVEFILE_PATH : TIK_PERSONALIZED_SAVEFILE_PATH, 0); if (!save_ctx) { - LOGFILE("Failed to open ES %s ticket system savefile!", title_key_type == TikTitleKeyType_Common ? "common" : "personalized"); + LOGFILE("Failed to open ES %s ticket system savefile!", titlekey_type == TikTitleKeyType_Common ? "common" : "personalized"); return false; } if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_SAVEFILE_STORAGE_PATH, &fat_storage, &ticket_bin_size)) { - LOGFILE("Failed to locate \"%s\" in ES %s ticket system save!", TIK_SAVEFILE_STORAGE_PATH, title_key_type == TikTitleKeyType_Common ? "common" : "personalized"); + LOGFILE("Failed to locate \"%s\" in ES %s ticket system save!", TIK_SAVEFILE_STORAGE_PATH, titlekey_type == TikTitleKeyType_Common ? "common" : "personalized"); goto out; } @@ -146,32 +258,40 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id) } ticket_bin_buf = malloc(buf_size); - if (ticket_bin_buf) + if (!ticket_bin_buf) { - LOGFILE("Unable to allocate memory for temporary read buffer!"); + LOGFILE("Unable to allocate 0x%lX bytes block for temporary read buffer!", buf_size); goto out; } - while(br == buf_size && total_br < ticket_bin_size) + while(total_br < ticket_bin_size) { + if (buf_size > (ticket_bin_size - total_br)) buf_size = (ticket_bin_size - total_br); + br = save_allocation_table_storage_read(&fat_storage, ticket_bin_buf, total_br, buf_size); if (br != buf_size) { - LOGFILE("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_SAVEFILE_STORAGE_PATH, title_key_type == TikTitleKeyType_Common ? "common" : "personalized"); + LOGFILE("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_SAVEFILE_STORAGE_PATH, \ + (titlekey_type == TikTitleKeyType_Common ? "common" : "personalized")); goto out; } - if (ticket_bin_buf[0] == 0) break; + total_br += br; for(i = 0; i < buf_size; i += TIK_MAX_SIZE) { - TikCommonBlock *tik_common_blk = tikGetTicketCommonBlockFromMemoryBuffer(ticket_bin_buf + i); - if (!tik_common_blk || memcmp(tik_common_blk->rights_id.c, id->c, 0x10) != 0) continue; + if ((buf_size - i) < TIK_MIN_SIZE) break; - /* Jackpot */ - found_tik = true; - break; + TikCommonBlock *tik_common_blk = tikGetCommonBlockFromMemoryBuffer(ticket_bin_buf + i); + if (tik_common_blk && !memcmp(tik_common_blk->rights_id.c, id->c, 0x10)) + { + /* Jackpot */ + found_tik = true; + break; + } } + + if (found_tik) break; } if (!found_tik) @@ -198,7 +318,43 @@ out: return success; } -bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TikCommonBlock *tik_common_blk) +static TikCommonBlock *tikGetCommonBlockFromMemoryBuffer(void *data) +{ + if (!data) + { + LOGFILE("Invalid parameters!"); + return NULL; + } + + u8 *data_u8 = (u8*)data; + TikCommonBlock *tik_common_blk = NULL; + u32 sig_type = 0; + + memcpy(&sig_type, data_u8, sizeof(u32)); + + switch(sig_type) + { + case SignatureType_Rsa4096Sha1: + case SignatureType_Rsa4096Sha256: + tik_common_blk = (TikCommonBlock*)(data_u8 + sizeof(SignatureBlockRsa4096)); + break; + case SignatureType_Rsa2048Sha1: + case SignatureType_Rsa2048Sha256: + tik_common_blk = (TikCommonBlock*)(data_u8 + sizeof(SignatureBlockRsa2048)); + break; + case SignatureType_Ecsda240Sha1: + case SignatureType_Ecsda240Sha256: + tik_common_blk = (TikCommonBlock*)(data_u8 + sizeof(SignatureBlockEcsda240)); + break; + default: + LOGFILE("Invalid signature type value! (0x%08X)", sig_type); + return NULL; + } + + return tik_common_blk; +} + +static bool tikGetTitleKeyFromTicketCommonBlock(void *dst, const TikCommonBlock *tik_common_blk) { if (!dst || !tik_common_blk) { @@ -210,11 +366,11 @@ bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TikCommonBlock *tik_common_b u8 out_keydata[0x100] = {0}; tikEticketDeviceKeyData *eticket_devkey = NULL; - switch(tik_common_blk->title_key_type) + switch(tik_common_blk->titlekey_type) { case TikTitleKeyType_Common: /* No titlekek crypto used */ - memcpy(dst, tik_common_blk->title_key_block, 0x10); + memcpy(dst, tik_common_blk->titlekey_block, 0x10); break; case TikTitleKeyType_Personalized: /* Retrieve eTicket device key */ @@ -227,7 +383,8 @@ bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TikCommonBlock *tik_common_b eticket_devkey = (tikEticketDeviceKeyData*)g_eTicketDeviceKey.key; /* Perform a RSA-OAEP decrypt operation to get the titlekey */ - if (!rsa2048OaepDecryptAndVerify(out_keydata, 0x100, tik_common_blk->title_key_block, eticket_devkey->modulus, eticket_devkey->exponent, 0x100, g_nullHash, &out_keydata_size) || out_keydata_size < 0x10) + if (!rsa2048OaepDecryptAndVerify(out_keydata, 0x100, tik_common_blk->titlekey_block, eticket_devkey->modulus, eticket_devkey->exponent, 0x100, g_nullHash, &out_keydata_size) || \ + out_keydata_size < 0x10) { LOGFILE("RSA-OAEP titlekey decryption failed!"); return false; @@ -238,14 +395,14 @@ bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TikCommonBlock *tik_common_b break; default: - LOGFILE("Invalid titlekey type value! (0x%02X)", tik_common_blk->title_key_type); + LOGFILE("Invalid titlekey type value! (0x%02X)", tik_common_blk->titlekey_type); return false; } return true; } -bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_generation) +static bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_generation) { if (!dst || !src) { @@ -269,137 +426,6 @@ bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_generati return true; } -bool tikGetTitleKekDecryptedTitleKeyFromTicket(void *dst, Ticket *tik) -{ - if (!dst || !tik) - { - LOGFILE("Invalid parameters!"); - return false; - } - - u8 titlekey[0x10] = {0}; - TikCommonBlock *tik_common_blk = NULL; - - tik_common_blk = tikGetTicketCommonBlockFromTicket(tik); - if (!tik_common_blk) - { - LOGFILE("Unable to retrieve ticket common block!"); - return false; - } - - if (!tikGetTitleKeyFromTicketCommonBlock(titlekey, tik_common_blk)) - { - LOGFILE("Unable to retrieve titlekey from ticket!"); - return false; - } - - /* Even though tickets do have a proper key_generation field, we'll just retrieve it from the rights_id field */ - /* Old custom tools used to wipe the key_generation field or save it to a different offset */ - if (!tikGetTitleKekDecryptedTitleKey(dst, titlekey, tik_common_blk->rights_id.c[0xF])) - { - LOGFILE("Unable to perform titlekek decryption on titlekey!"); - return false; - } - - return true; -} - -void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, const void *titlekey) -{ - if (!tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE || !titlekey) return; - - bool dev_cert = false; - TikCommonBlock *tik_common_blk = NULL; - - tik_common_blk = tikGetTicketCommonBlockFromTicket(tik); - if (!tik_common_blk || tik_common_blk->title_key_type != TikTitleKeyType_Personalized) return; - - switch(tik->type) - { - case TikType_SigRsa4096: - tik->size = sizeof(TikSigRsa4096); - memset(tik->data + 4, 0xFF, MEMBER_SIZE(SignatureBlockRsa4096, signature)); - break; - case TikType_SigRsa2048: - tik->size = sizeof(TikSigRsa2048); - memset(tik->data + 4, 0xFF, MEMBER_SIZE(SignatureBlockRsa2048, signature)); - break; - case TikType_SigEcsda240: - tik->size = sizeof(TikSigEcsda240); - memset(tik->data + 4, 0xFF, MEMBER_SIZE(SignatureBlockEcsda240, signature)); - break; - default: - break; - } - - dev_cert = (strstr(tik_common_blk->issuer, "CA00000004") != NULL); - - memset(tik_common_blk->issuer, 0, sizeof(tik_common_blk->issuer)); - sprintf(tik_common_blk->issuer, "Root-CA%08X-XS00000020", dev_cert ? 4 : 3); - - memset(tik_common_blk->title_key_block, 0, sizeof(tik_common_blk->title_key_block)); - memcpy(tik_common_blk->title_key_block, titlekey, 0x10); - - tik_common_blk->title_key_type = TikTitleKeyType_Common; - tik_common_blk->ticket_id = 0; - tik_common_blk->device_id = 0; - tik_common_blk->account_id = 0; - - tik_common_blk->sect_total_size = 0; - tik_common_blk->sect_hdr_offset = (u32)tik->size; - tik_common_blk->sect_hdr_count = 0; - tik_common_blk->sect_hdr_entry_size = 0; - - memset(tik->data + tik->size, 0, TIK_MAX_SIZE - tik->size); -} - -static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized) -{ - if (!out || !out_count) - { - LOGFILE("Invalid parameters!"); - return false; - } - - Result rc = 0; - u32 count = 0, ids_written = 0; - FsRightsId *rights_ids = NULL; - - rc = (personalized ? esCountPersonalizedTicket((s32*)&count) : esCountCommonTicket((s32*)&count)); - if (R_FAILED(rc)) - { - LOGFILE("esCount%sTicket failed! (0x%08X)", personalized ? "Personalized" : "Common", rc); - return false; - } - - if (!count) - { - LOGFILE("No %s tickets available!", personalized ? "personalized" : "common"); - *out_count = 0; - return true; - } - - rights_ids = calloc(count, sizeof(FsRightsId)); - if (!rights_ids) - { - LOGFILE("Unable to allocate memory for %s rights IDs!", personalized ? "personalized" : "common"); - return false; - } - - rc = (personalized ? esListPersonalizedTicket((s32*)&ids_written, rights_ids, count * sizeof(FsRightsId)) : esListCommonTicket((s32*)&ids_written, rights_ids, count * sizeof(FsRightsId))); - if (R_FAILED(rc) || ids_written != count) - { - LOGFILE("esList%sTicket failed! (0x%08X) | Wrote %u entries, expected %u entries", personalized ? "Personalized" : "Common", rc, ids_written, count); - free(rights_ids); - return false; - } - - *out = rights_ids; - *out_count = count; - - return true; -} - static u8 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id) { if (!id) @@ -442,6 +468,53 @@ static u8 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id) return type; } +static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized) +{ + if (!out || !out_count) + { + LOGFILE("Invalid parameters!"); + return false; + } + + Result rc = 0; + u32 count = 0, ids_written = 0; + FsRightsId *rights_ids = NULL; + + rc = (personalized ? esCountPersonalizedTicket((s32*)&count) : esCountCommonTicket((s32*)&count)); + if (R_FAILED(rc)) + { + LOGFILE("esCount%sTicket failed! (0x%08X)", personalized ? "Personalized" : "Common", rc); + return false; + } + + if (!count) + { + LOGFILE("No %s tickets available!", personalized ? "personalized" : "common"); + *out_count = 0; + return true; + } + + rights_ids = calloc(count, sizeof(FsRightsId)); + if (!rights_ids) + { + LOGFILE("Unable to allocate memory for %s rights IDs!", personalized ? "personalized" : "common"); + return false; + } + + rc = (personalized ? esListPersonalizedTicket((s32*)&ids_written, rights_ids, (s32)count) : esListCommonTicket((s32*)&ids_written, rights_ids, (s32)count)); + if (R_FAILED(rc) || ids_written != count) + { + LOGFILE("esList%sTicket failed! (0x%08X) | Wrote %u entries, expected %u entries", personalized ? "Personalized" : "Common", rc, ids_written, count); + free(rights_ids); + return false; + } + + *out = rights_ids; + *out_count = count; + + return true; +} + static bool tikGetTicketTypeAndSize(const void *data, u64 data_size, u8 *out_type, u64 *out_size) { if (!data || data_size < TIK_MIN_SIZE || data_size > TIK_MAX_SIZE || !out_type || !out_size) @@ -453,7 +526,7 @@ static bool tikGetTicketTypeAndSize(const void *data, u64 data_size, u8 *out_typ u8 type = TikType_Invalid; const u8 *data_u8 = (const u8*)data; const TikCommonBlock *tik_common_blk = NULL; - u32 sig_type; + u32 sig_type = 0; u64 offset = 0; memcpy(&sig_type, data_u8, sizeof(u32)); @@ -509,46 +582,6 @@ static bool tikGetTicketTypeAndSize(const void *data, u64 data_size, u8 *out_typ return true; } -static bool tikTestKeyPairFromEticketDeviceKey(const void *e, const void *d, const void *n) -{ - if (!e || !d || !n) - { - LOGFILE("Invalid parameters!"); - return false; - } - - Result rc = 0; - u8 x[0x100] = {0}, y[0x100] = {0}, z[0x100] = {0}; - - /* 0xCAFEBABE */ - x[0xFC] = 0xCA; - x[0xFD] = 0xFE; - x[0xFE] = 0xBA; - x[0xFF] = 0xBE; - - rc = splUserExpMod(x, n, d, 0x100, y); - if (R_FAILED(rc)) - { - LOGFILE("splUserExpMod failed! (#1) (0x%08X)", rc); - return false; - } - - rc = splUserExpMod(y, n, e, 4, z); - if (R_FAILED(rc)) - { - LOGFILE("splUserExpMod failed! (#2) (0x%08X)", rc); - return false; - } - - if (memcmp(x, y, 0x100) != 0) - { - LOGFILE("Invalid RSA key pair!"); - return false; - } - - return true; -} - static bool tikRetrieveEticketDeviceKey(void) { if (g_eTicketDeviceKeyRetrieved) return true; @@ -590,3 +623,43 @@ static bool tikRetrieveEticketDeviceKey(void) return true; } + +static bool tikTestKeyPairFromEticketDeviceKey(const void *e, const void *d, const void *n) +{ + if (!e || !d || !n) + { + LOGFILE("Invalid parameters!"); + return false; + } + + Result rc = 0; + u8 x[0x100] = {0}, y[0x100] = {0}, z[0x100] = {0}; + + /* 0xCAFEBABE */ + x[0xFC] = 0xCA; + x[0xFD] = 0xFE; + x[0xFE] = 0xBA; + x[0xFF] = 0xBE; + + rc = splUserExpMod(x, n, d, 0x100, y); + if (R_FAILED(rc)) + { + LOGFILE("splUserExpMod failed! (#1) (0x%08X)", rc); + return false; + } + + rc = splUserExpMod(y, n, e, 4, z); + if (R_FAILED(rc)) + { + LOGFILE("splUserExpMod failed! (#2) (0x%08X)", rc); + return false; + } + + if (memcmp(x, z, 0x100) != 0) + { + LOGFILE("Invalid RSA key pair!"); + return false; + } + + return true; +} diff --git a/source/tik.h b/source/tik.h index 62e1b56..3a99b21 100644 --- a/source/tik.h +++ b/source/tik.h @@ -68,9 +68,9 @@ typedef enum { /// Placed after the ticket signature block. typedef struct { char issuer[0x40]; - u8 title_key_block[0x100]; + u8 titlekey_block[0x100]; u8 format_version; - u8 title_key_type; ///< TikTitleKeyType. + u8 titlekey_type; ///< TikTitleKeyType. u16 ticket_version; u8 license_type; ///< TikLicenseType. u8 key_generation; @@ -112,38 +112,24 @@ typedef struct { u16 section_type; ///< TikSectionType. } TikEsv2SectionRecord; -/// Used to store ticket type, size and raw data. +/// Used to store ticket type, size and raw data, as well as titlekey data. typedef struct { u8 type; ///< TikType. - u64 size; - u8 data[TIK_MAX_SIZE]; + u64 size; ///< Raw ticket size. + u8 data[TIK_MAX_SIZE]; ///< Raw ticket data. + u8 enc_titlekey[0x10]; ///< Titlekey with titlekek crypto (RSA-OAEP unwrapped if dealing with a TikTitleKeyType_Personalized ticket). + u8 dec_titlekey[0x10]; ///< Titlekey without titlekek crypto. Ready to use for NCA FS section decryption. } Ticket; -TikCommonBlock *tikGetTicketCommonBlockFromMemoryBuffer(void *data); +/// Retrieves a ticket from either the secure hash FS partition from the inserted gamecard or ES ticket savedata using a Rights ID value. +/// Titlekey is also RSA-OAEP unwrapped (if needed) and titlekek decrypted right away. +bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gamecard); -static inline TikCommonBlock *tikGetTicketCommonBlockFromTicket(Ticket *tik) -{ - if (!tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE) return NULL; - return tikGetTicketCommonBlockFromMemoryBuffer(tik->data); -} - -bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id); - -bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TikCommonBlock *tik_common_blk); - -static inline bool tikGetTitleKeyFromTicket(void *dst, Ticket *tik) -{ - if (!dst || !tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE) return false; - return tikGetTitleKeyFromTicketCommonBlock(dst, tikGetTicketCommonBlockFromTicket(tik)); -} - -bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_generation); - -bool tikGetTitleKekDecryptedTitleKeyFromTicket(void *dst, Ticket *tik); +/// Retrieves the common block from an input Ticket. +TikCommonBlock *tikGetCommonBlockFromTicket(Ticket *tik); /// This will convert a TikTitleKeyType_Personalized ticket into a TikTitleKeyType_Common ticket. -/// Use the output titlekey from tikGetTitleKeyFromTicket() / tikGetTitleKeyFromTicketCommonBlock() as the second parameter for this function. /// Bear in mind the 'size' member from the Ticket parameter will be updated by this function to remove any possible references to TikEsv2SectionRecord records. -void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, const void *titlekey); +void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik); #endif /* __TIK_H__ */ diff --git a/source/utils.c b/source/utils.c index d4e70cc..447d5f2 100644 --- a/source/utils.c +++ b/source/utils.c @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include "gamecard.h" #include "services.h" #include "utils.h" +#include "fatfs/ff.h" /* Global variables. */ @@ -35,12 +37,18 @@ static AppletHookCookie g_systemOverclockCookie = {0}; static Mutex g_logfileMutex = 0; +static FsStorage g_emmcBisSystemPartitionStorage = {0}; +static FATFS *g_emmcBisSystemPartitionFs = NULL; + /* Function prototypes. */ static void _utilsGetCustomFirmwareType(void); static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param); +static bool utilsMountEmmcBisSystemPartitionStorage(void); +static void utilsUnmountEmmcBisSystemPartitionStorage(void); + u64 utilsHidKeysAllDown(void) { u8 controller; @@ -125,6 +133,17 @@ bool utilsInitializeResources(void) return false; } + /* Initialize gamecard interface */ + Result rc = gamecardInitialize(); + if (R_FAILED(rc)) + { + LOGFILE("Failed to initialize gamecard interface!"); + return false; + } + + /* Mount eMMC BIS System partition */ + if (!utilsMountEmmcBisSystemPartitionStorage()) return false; + /* Initialize FreeType */ //if (!freeTypeHelperInitialize()) return false; @@ -140,22 +159,11 @@ bool utilsInitializeResources(void) /* Setup an applet hook to change the hardware clocks after a system mode change (docked <-> undocked) */ appletHook(&g_systemOverclockCookie, utilsOverclockSystemAppletHook, NULL); - /* Initialize gamecard interface */ - Result rc = gamecardInitialize(); - if (R_FAILED(rc)) - { - LOGFILE("Failed to initialize gamecard interface!"); - return false; - } - return true; } void utilsCloseResources(void) { - /* Deinitialize gamecard interface */ - gamecardExit(); - /* Unset our overclock applet hook */ appletUnhook(&g_systemOverclockCookie); @@ -168,6 +176,12 @@ void utilsCloseResources(void) /* Free FreeType resouces */ //freeTypeHelperExit(); + /* Unmount eMMC BIS System partition */ + utilsUnmountEmmcBisSystemPartitionStorage(); + + /* Deinitialize gamecard interface */ + gamecardExit(); + /* Close initialized services */ servicesClose(); } @@ -177,6 +191,30 @@ u8 utilsGetCustomFirmwareType(void) return g_customFirmwareType; } +void utilsGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, size_t src_size) +{ + if (!src || !src_size || !dst || dst_size < ((src_size * 2) + 1)) return; + + size_t i, j; + const u8 *src_u8 = (const u8*)src; + + for(i = 0, j = 0; i < src_size; i++) + { + char nib1 = ((src_u8[i] >> 4) & 0xF); + char nib2 = (src_u8[i] & 0xF); + + dst[j++] = (nib1 + (nib1 < 0xA ? 0x30 : 0x57)); + dst[j++] = (nib2 + (nib2 < 0xA ? 0x30 : 0x57)); + } + + dst[j] = '\0'; +} + +FsStorage *utilsGetEmmcBisSystemPartitionStorage(void) +{ + return &g_emmcBisSystemPartitionStorage; +} + static void _utilsGetCustomFirmwareType(void) { bool tx_srv = servicesCheckRunningServiceByName("tx"); @@ -206,3 +244,48 @@ static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param) /* To do: read config here to actually know the value to use with utilsOverclockSystem */ utilsOverclockSystem(true); } + +static bool utilsMountEmmcBisSystemPartitionStorage(void) +{ + Result rc = 0; + FRESULT fr = FR_OK; + + rc = fsOpenBisStorage(&g_emmcBisSystemPartitionStorage, FsBisPartitionId_System); + if (R_FAILED(rc)) + { + LOGFILE("Failed to open eMMC BIS System partition storage! (0x%08X)", rc); + return false; + } + + g_emmcBisSystemPartitionFs = calloc(1, sizeof(FATFS)); + if (!g_emmcBisSystemPartitionFs) + { + LOGFILE("Unable to allocate memory for FatFs object!"); + return false; + } + + fr = f_mount(g_emmcBisSystemPartitionFs, BIS_SYSTEM_PARTITION_MOUNT_NAME, 1); + if (fr != FR_OK) + { + LOGFILE("Failed to mount eMMC BIS System partition! (%u)", fr); + return false; + } + + return true; +} + +static void utilsUnmountEmmcBisSystemPartitionStorage(void) +{ + if (g_emmcBisSystemPartitionFs) + { + f_unmount(BIS_SYSTEM_PARTITION_MOUNT_NAME); + free(g_emmcBisSystemPartitionFs); + g_emmcBisSystemPartitionFs = NULL; + } + + if (serviceIsActive(&(g_emmcBisSystemPartitionStorage.s))) + { + fsStorageClose(&g_emmcBisSystemPartitionStorage); + memset(&g_emmcBisSystemPartitionStorage, 0, sizeof(FsStorage)); + } +} diff --git a/source/utils.h b/source/utils.h index 144b97c..525c1ea 100644 --- a/source/utils.h +++ b/source/utils.h @@ -21,19 +21,17 @@ #include -#define APP_BASE_PATH "sdmc:/switch/nxdumptool/" +#define APP_BASE_PATH "sdmc:/switch/nxdumptool/" -#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__) +#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__) -#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member) +#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member) -#define SLEEP(x) svcSleepThread((x) * (u64)1000000000) - -#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) - -#define ROUND_UP(x, y) ((x) + (((y) - ((x) % (y))) % (y))) /* Aligns 'x' bytes to a 'y' bytes boundary. */ +#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) +#define ROUND_UP(x, y) ((x) + (((y) - ((x) % (y))) % (y))) /* Aligns 'x' bytes to a 'y' bytes boundary. */ +#define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:" typedef enum { UtilsCustomFirmwareType_Unknown = 0, @@ -66,11 +64,13 @@ void utilsCloseResources(void); u8 utilsGetCustomFirmwareType(void); ///< UtilsCustomFirmwareType. +void utilsGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, size_t src_size); +FsStorage *utilsGetEmmcBisSystemPartitionStorage(void); -static inline FsStorage *utilsGetEmmcBisSystemStorage(void) +static inline void utilsSleep(u64 seconds) { - return NULL; + if (seconds) svcSleepThread(seconds * (u64)1000000000); }