mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-08 11:51:48 +00:00
Mooooore baby steps.
I can finally continue the NCA handler.
This commit is contained in:
parent
9679eb72bb
commit
1b45cdf05a
13 changed files with 725 additions and 435 deletions
238
source/cert.c
238
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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
533
source/tik.c
533
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;
|
||||
}
|
||||
|
|
40
source/tik.h
40
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__ */
|
||||
|
|
105
source/utils.c
105
source/utils.c
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,19 +21,17 @@
|
|||
|
||||
#include <switch.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue