diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index 9f207c2..e5d3109 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -52,6 +52,7 @@ #define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB)) #define FTYPE_KEYBUILD(tp) (tp&(BIN_KEYDB|BIN_LEGKEY)) #define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS|GAME_GBA|GAME_TAD|GAME_3DSX)) +#define FTYPE_CIACHECK(tp) (tp&GAME_CIA) #define FTYPE_RENAMABLE(tp) (tp&(GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_NDS|GAME_GBA)) #define FTYPE_TRIMABLE(tp) (tp&(IMG_NAND|GAME_NCCH|GAME_NCSD|GAME_NDS|SYS_FIRM)) #define FTYPE_TRANSFERABLE(tp) ((u64) (tp&(IMG_FAT|FLAG_CTR)) == (u64) (IMG_FAT|FLAG_CTR)) diff --git a/arm9/source/game/cert.c b/arm9/source/game/cert.c new file mode 100644 index 0000000..3b01f0f --- /dev/null +++ b/arm9/source/game/cert.c @@ -0,0 +1,21 @@ +#include "cert.h" +#include "ff.h" + +u32 LoadCertFromCertDb(u64 offset, Certificate* cert, u8* mod, u32* exp) { + Certificate cert_local; + FIL db; + UINT bytes_read; + + // not much in terms of error checking here + if (f_open(&db, "1:/dbs/certs.db", FA_READ | FA_OPEN_EXISTING) != FR_OK) + return 1; + f_lseek(&db, offset); + if (!cert) cert = &cert_local; + f_read(&db, cert, CERT_SIZE, &bytes_read); + f_close(&db); + + if (mod) memcpy(mod, cert->mod, 0x100); + if (exp) *exp = getle32(cert->exp); + + return 0; +} diff --git a/arm9/source/game/cert.h b/arm9/source/game/cert.h new file mode 100644 index 0000000..ad7e73d --- /dev/null +++ b/arm9/source/game/cert.h @@ -0,0 +1,22 @@ +#pragma once + +#include "common.h" + +#define CERT_SIZE sizeof(Certificate) + +// from: http://3dbrew.org/wiki/Certificates +// all numbers in big endian +typedef struct { + u8 sig_type[4]; // expected: 0x010004 / RSA_2048 SHA256 + u8 signature[0x100]; + u8 padding0[0x3C]; + u8 issuer[0x40]; + u8 keytype[4]; // expected: 0x01 / RSA_2048 + u8 name[0x40]; + u8 unknown[4]; + u8 mod[0x100]; + u8 exp[0x04]; + u8 padding1[0x34]; +} __attribute__((packed)) Certificate; + +u32 LoadCertFromCertDb(u64 offset, Certificate* cert, u8* mod, u32* exp); diff --git a/arm9/source/game/ticket.c b/arm9/source/game/ticket.c index d0170df..f128029 100644 --- a/arm9/source/game/ticket.c +++ b/arm9/source/game/ticket.c @@ -1,5 +1,6 @@ #include "ticket.h" #include "unittype.h" +#include "cert.h" #include "sha.h" #include "rsa.h" #include "ff.h" @@ -19,19 +20,11 @@ u32 ValidateTicketSignature(Ticket* ticket) { static u8 mod[0x100] = { 0 }; static u32 exp = 0; - // grab cert from cert.db if (!got_modexp) { - Certificate cert; - FIL db; - UINT bytes_read; - if (f_open(&db, "1:/dbs/certs.db", FA_READ | FA_OPEN_EXISTING) != FR_OK) - return 1; - f_lseek(&db, 0x3F10); - f_read(&db, &cert, CERT_SIZE, &bytes_read); - f_close(&db); - memcpy(mod, cert.mod, 0x100); - exp = getle32(cert.exp); - got_modexp = true; + // grab mod/exp from cert from cert.db + if (LoadCertFromCertDb(0x3F10, NULL, mod, &exp) == 0) + got_modexp = true; + else return 1; } if (!RSA_setKey2048(3, mod, exp) || diff --git a/arm9/source/game/ticket.h b/arm9/source/game/ticket.h index d66f695..ae6fcec 100644 --- a/arm9/source/game/ticket.h +++ b/arm9/source/game/ticket.h @@ -3,7 +3,6 @@ #include "common.h" #define TICKET_SIZE sizeof(Ticket) -#define CERT_SIZE sizeof(Certificate) #define TICKET_CDNCERT_SIZE 0x700 #define TICKET_ISSUER "Root-CA00000003-XS0000000c" @@ -44,21 +43,6 @@ typedef struct { u8 content_index[0xAC]; } __attribute__((packed)) Ticket; -// from: http://3dbrew.org/wiki/Certificates -// all numbers in big endian -typedef struct { - u8 sig_type[4]; // expected: 0x010004 / RSA_2048 SHA256 - u8 signature[0x100]; - u8 padding0[0x3C]; - u8 issuer[0x40]; - u8 keytype[4]; // expected: 0x01 / RSA_2048 - u8 name[0x40]; - u8 unknown[4]; - u8 mod[0x100]; - u8 exp[0x04]; - u8 padding1[0x34]; -} __attribute__((packed)) Certificate; - u32 ValidateTicket(Ticket* ticket); u32 ValidateTicketSignature(Ticket* ticket); u32 BuildFakeTicket(Ticket* ticket, u8* title_id); diff --git a/arm9/source/game/tmd.c b/arm9/source/game/tmd.c index 42d2d5e..adac28d 100644 --- a/arm9/source/game/tmd.c +++ b/arm9/source/game/tmd.c @@ -1,6 +1,8 @@ #include "tmd.h" #include "unittype.h" +#include "cert.h" #include "sha.h" +#include "rsa.h" #include "ff.h" u32 ValidateTmd(TitleMetaData* tmd) { @@ -12,6 +14,25 @@ u32 ValidateTmd(TitleMetaData* tmd) { return 0; } +u32 ValidateTmdSignature(TitleMetaData* tmd) { + static bool got_modexp = false; + static u8 mod[0x100] = { 0 }; + static u32 exp = 0; + + if (!got_modexp) { + // grab mod/exp from cert from cert.db + if (LoadCertFromCertDb(0x3C10, NULL, mod, &exp) == 0) + got_modexp = true; + else return 1; + } + + if (!RSA_setKey2048(3, mod, exp) || + !RSA_verify2048((void*) &(tmd->signature), (void*) &(tmd->issuer), 0xC4)) + return 1; + + return 0; +} + u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) { memset(ctr, 0, 16); memcpy(ctr, chunk->index, 2); diff --git a/arm9/source/game/tmd.h b/arm9/source/game/tmd.h index afdcf08..823e18f 100644 --- a/arm9/source/game/tmd.h +++ b/arm9/source/game/tmd.h @@ -58,6 +58,7 @@ typedef struct { } __attribute__((packed)) TitleMetaData; u32 ValidateTmd(TitleMetaData* tmd); +u32 ValidateTmdSignature(TitleMetaData* tmd); u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk); u32 FixTmdHashes(TitleMetaData* tmd); u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size); diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 19ac675..a400032 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -1028,6 +1028,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path; bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path && !((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND)); bool titleinfo = (FTYPE_TITLEINFO(filetype)); + bool ciacheckable = (FTYPE_CIACHECK(filetype)); bool renamable = (FTYPE_RENAMABLE(filetype)); bool trimable = (FTYPE_TRIMABLE(filetype)) && !(drvtype & DRV_VIRTUAL) && !(drvtype & DRV_ALIAS) && !(drvtype & DRV_CTRNAND) && !(drvtype & DRV_TWLNAND) && !(drvtype & DRV_IMAGE); @@ -1230,6 +1231,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan // stuff for special menu starts here n_opt = 0; int show_info = (titleinfo) ? ++n_opt : -1; + int ciacheck = (ciacheckable) ? ++n_opt : -1; int mount = (mountable) ? ++n_opt : -1; int restore = (restorable) ? ++n_opt : -1; int ebackup = (ebackupable) ? ++n_opt : -1; @@ -1266,6 +1268,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan if (ebackup > 0) optionstr[ebackup-1] = "Update embedded backup"; if (ncsdfix > 0) optionstr[ncsdfix-1] = "Rebuild NCSD header"; if (show_info > 0) optionstr[show_info-1] = "Show title info"; + if (ciacheck > 0) optionstr[ciacheck-1] = "CIA checker tool"; if (decrypt > 0) optionstr[decrypt-1] = (cryptable_inplace) ? "Decrypt file (...)" : "Decrypt file (" OUTPUT_PATH ")"; if (encrypt > 0) optionstr[encrypt-1] = (cryptable_inplace) ? "Encrypt file (...)" : "Encrypt file (" OUTPUT_PATH ")"; if (cia_build > 0) optionstr[cia_build-1] = (cia_build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)"; @@ -1647,6 +1650,10 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan ShowPrompt(false, "Title info: not found"); return 0; } + else if (user_select == ciacheck) { // -> CIA checker tool + ShowCiaCheckerInfo(file_path); + return 0; + } else if (user_select == hsinject) { // -> Inject to Health & Safety char* destdrv[2] = { NULL }; n_opt = 0; diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index 3f4bf80..3f2ce9f 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -1816,6 +1816,73 @@ u32 ShowGameFileTitleInfo(const char* path) { return ret; } +u32 ShowCiaCheckerInfo(const char* path) { + CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); + if (!cia) return 1; + + // path string + char pathstr[32 + 1]; + TruncateString(pathstr, path, 32, 8); + + // load CIA stub + if (LoadCiaStub(cia, path) != 0) { + ShowPrompt(false, "%s\nError: Probably not a CIA file", pathstr); + free(cia); + return 1; + } + + // states: 0 -> invalid / 1 -> valid / badsig / 2 -> valid / goodsig + u32 state_ticket = 0; + u32 state_tmd = 0; + u32 content_count = getbe16(cia->tmd.content_count); + u32 content_found = 0; + u64 title_id = getbe64(cia->ticket.title_id); + u32 console_id = getbe32(cia->ticket.console_id); + bool missing_first = false; + bool is_dlc = ((title_id >> 32) == 0x0004008C); + + // check ticket + if (ValidateTicket(&(cia->ticket)) == 0) + state_ticket = (ValidateTicketSignature(&(cia->ticket)) == 0) ? 2 : 1; + + // check tmd + if (ValidateTmd(&(cia->tmd)) == 0) + state_tmd = (ValidateTmdSignature(&(cia->tmd)) == 0) ? 2 : 1; + + // check for available contents + u8* cnt_index = cia->header.content_index; + for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) { + TmdContentChunk* chunk = &(cia->content_list[i]); + u16 index = getbe16(chunk->index); + if (cnt_index[index/8] & (1 << (7-(index%8)))) content_found++; + else if (i == 0) missing_first = true; + } + + // CIA type string + char typestr[32]; + if (!state_ticket || !state_tmd || missing_first || (!is_dlc && (content_found != content_count))) + snprintf(typestr, 32, "Possibly Broken"); + else snprintf(typestr, 32, "%s %s%s", + console_id ? "Personal" : "Universal", + (state_ticket == 2) ? "Legit" : (state_tmd == 2) ? "Pirate Legit" : "Custom", + is_dlc ? " DLC" : ""); + + // output results + s32 state_verify = -1; + while (true) { + if (!ShowPrompt(state_verify < 0, "%s\n%s CIA File\n \nTitle ID: %016llX\nConsole ID: %08lX\nContents in CIA: %lu/%lu\nTicket/TMD: %s/%s\nVerification: %s", + pathstr, typestr, title_id, console_id, + content_found, content_count, + (state_ticket == 0) ? "invalid" : (state_ticket == 2) ? "legit" : "illegit", + (state_tmd == 0) ? "invalid" : (state_tmd == 2) ? "legit" : "illegit", + (state_verify < 0) ? "pending\n \nProceed with verification?" : (state_verify == 0) ? "passed" : "failed") || + (state_verify >= 0)) break; + state_verify = VerifyCiaFile(path); + } + + return (state_ticket && state_tmd) ? 0 : 1; +} + u32 BuildNcchInfoXorpads(const char* destdir, const char* path) { FIL fp_info; FIL fp_xorpad; diff --git a/arm9/source/utils/gameutil.h b/arm9/source/utils/gameutil.h index 5d28d67..b97d34b 100644 --- a/arm9/source/utils/gameutil.h +++ b/arm9/source/utils/gameutil.h @@ -12,6 +12,7 @@ u32 ExtractDataFromDisaDiff(const char* path); u64 GetGameFileTrimmedSize(const char* path); u32 TrimGameFile(const char* path); u32 ShowGameFileTitleInfo(const char* path); +u32 ShowCiaCheckerInfo(const char* path); u32 GetTmdContentPath(char* path_content, const char* path_tmd); u32 BuildNcchInfoXorpads(const char* destdir, const char* path); u32 CheckHealthAndSafetyInject(const char* hsdrv);