1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 18:26:39 +00:00

Mooooore baby steps.

I can finally continue the NCA handler.
This commit is contained in:
Pablo Curiel 2020-04-17 17:59:05 -04:00
parent 9679eb72bb
commit 1b45cdf05a
13 changed files with 725 additions and 435 deletions

View file

@ -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;
}
char *pch = strtok(issuer_copy, "-");
while(pch != NULL)
{
if (!certRetrieveCertificateByName(&(dst->certs[i]), pch))
{
LOGFILE("Unable to retrieve certificate \"%s\"!", pch);
success = false;
break;
}
void certFreeCertificateChain(CertificateChain *chain)
{
if (!chain || !chain->certs) return;
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;

View file

@ -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);

View file

@ -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;

View file

@ -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);
}

View file

@ -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)

View file

@ -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__ */

View file

@ -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.

View file

@ -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:

View file

@ -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);

View file

@ -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,34 +258,42 @@ 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;
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)
{
LOGFILE("Unable to find a matching ticket entry for the provided Rights ID!");
@ -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;
}

View file

@ -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__ */

View file

@ -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));
}
}

View file

@ -27,13 +27,11 @@
#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 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);
}