Enable optional signature verification for NCCH & NCSD

This commit is contained in:
d0k3 2025-10-19 14:38:20 +02:00
parent 748c46ed5a
commit ceabad8b3f
11 changed files with 84 additions and 16 deletions

View File

@ -2,6 +2,8 @@
#include "keydb.h" #include "keydb.h"
#include "aes.h" #include "aes.h"
#include "sha.h" #include "sha.h"
#include "rsa.h"
#include "itcm.h"
#define EXEFS_KEYID(name) (((strncmp(name, "banner", 8) == 0) || (strncmp(name, "icon", 8) == 0)) ? 0 : 1) #define EXEFS_KEYID(name) (((strncmp(name, "banner", 8) == 0) || (strncmp(name, "icon", 8) == 0)) ? 0 : 1)
@ -28,6 +30,26 @@ u32 ValidateNcchHeader(NcchHeader* header) {
return 0; return 0;
} }
u32 ValidateNcchSignature(NcchHeader* header, NcchExtHeader* exthdr)
{
u8 exp[4] = { 0x00, 0x01, 0x00, 0x01 };
u8* pubkey = ARM9_ITCM->rsaModulusCartNCSD;
if (exthdr) {
// check extheader signature
if (!RSA_setKey2048(3, (const u32*)(const void*)ARM9_ITCM->rsaModulusAccessDesc, getle32(exp)) ||
!RSA_verify2048((const u32*)(const void*)&exthdr->signature[0], (const u32*)(const void*)&exthdr->public_key[0], 0x300))
return 1;
pubkey = exthdr->public_key;
}
// check NCCH header signature
if (!RSA_setKey2048(3, (const u32*)(const void*)&pubkey[0], getle32(exp)) ||
!RSA_verify2048((const u32*)(const void*)&header->signature[0], (const u32*)(const void*)&((u8*)header)[0x100], 0x100))
return 1;
return 0;
}
u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 section) { u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 section) {
memset(ctr, 0x00, 16); memset(ctr, 0x00, 16);
if (ncch->version == 1) { if (ncch->version == 1) {

View File

@ -84,6 +84,7 @@ typedef struct {
} __attribute__((packed, aligned(16))) NcchHeader; } __attribute__((packed, aligned(16))) NcchHeader;
u32 ValidateNcchHeader(NcchHeader* header); u32 ValidateNcchHeader(NcchHeader* header);
u32 ValidateNcchSignature(NcchHeader* header, NcchExtHeader* exthdr);
u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid); u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid);
u32 SetupNcchCrypto(NcchHeader* ncch, u16 crypt_to); u32 SetupNcchCrypto(NcchHeader* ncch, u16 crypt_to);
u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs, u16 crypto); u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs, u16 crypto);

View File

@ -1,5 +1,7 @@
#include "ncsd.h" #include "ncsd.h"
#include "ncch.h" #include "ncch.h"
#include "rsa.h"
#include "itcm.h"
u32 ValidateNcsdHeader(NcsdHeader* header) { u32 ValidateNcsdHeader(NcsdHeader* header) {
static const u8 zeroes[16] = { 0 }; static const u8 zeroes[16] = { 0 };
@ -22,6 +24,15 @@ u32 ValidateNcsdHeader(NcsdHeader* header) {
return 0; return 0;
} }
u32 ValidateNcsdSignature(NcsdHeader* header) {
u8 exp[4] = { 0x00, 0x01, 0x00, 0x01 };
// this will fail for non-cart NCSDs anyways, so we don't need to check
if (!RSA_setKey2048(3, (const u32*)(const void*)&ARM9_ITCM->rsaModulusCartNCSD[0], getle32(exp)) ||
!RSA_verify2048((const u32*)(const void*)&header->signature[0], (const u32*)(const void*)&((u8*)header)[0x100], 0x100))
return 1;
return 0;
}
u64 GetNcsdTrimmedSize(NcsdHeader* header) { u64 GetNcsdTrimmedSize(NcsdHeader* header) {
u32 data_units = 0; u32 data_units = 0;
for (u32 i = 0; i < 8; i++) { for (u32 i = 0; i < 8; i++) {

View File

@ -37,5 +37,6 @@ typedef struct {
} PACKED_STRUCT NcsdHeader; } PACKED_STRUCT NcsdHeader;
u32 ValidateNcsdHeader(NcsdHeader* header); u32 ValidateNcsdHeader(NcsdHeader* header);
u32 ValidateNcsdSignature(NcsdHeader* header);
u64 GetNcsdTrimmedSize(NcsdHeader* header); u64 GetNcsdTrimmedSize(NcsdHeader* header);
u32 CryptNcsdSequential(void* data, u32 offset_data, u32 size_data, u16 crypto); u32 CryptNcsdSequential(void* data, u32 offset_data, u32 size_data, u16 crypto);

View File

@ -1744,7 +1744,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
ShowPrompt(false, STR_PATH_TYPE_BUILD_FAILED, pathstr, type); ShowPrompt(false, STR_PATH_TYPE_BUILD_FAILED, pathstr, type);
if ((filetype & (GAME_NCCH|GAME_NCSD)) && if ((filetype & (GAME_NCCH|GAME_NCSD)) &&
ShowPrompt(true, "%s\n%s", pathstr, STR_FILE_FAILED_CONVERSION_VERIFY_NOW)) { ShowPrompt(true, "%s\n%s", pathstr, STR_FILE_FAILED_CONVERSION_VERIFY_NOW)) {
ShowPrompt(false, "%s\n%s", pathstr, (VerifyGameFile(file_path) == 0) ? STR_VERIFICATION_SUCCESS : STR_VERIFICATION_FAILED); ShowPrompt(false, "%s\n%s", pathstr, (VerifyGameFile(file_path, false) == 0) ? STR_VERIFICATION_SUCCESS : STR_VERIFICATION_FAILED);
} }
} }
} }
@ -1797,7 +1797,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
ShowPrompt(false, "%s\n%s", pathstr, (ret == 0) ? STR_INSTALL_SUCCESS : STR_INSTALL_FAILED); ShowPrompt(false, "%s\n%s", pathstr, (ret == 0) ? STR_INSTALL_SUCCESS : STR_INSTALL_FAILED);
if ((ret != 0) && (filetype & (GAME_NCCH|GAME_NCSD)) && if ((ret != 0) && (filetype & (GAME_NCCH|GAME_NCSD)) &&
ShowPrompt(true, "%s\n%s", pathstr, STR_FILE_FAILED_INSTALL_VERIFY_NOW)) { ShowPrompt(true, "%s\n%s", pathstr, STR_FILE_FAILED_INSTALL_VERIFY_NOW)) {
ShowPrompt(false, "%s\n%s", pathstr, (VerifyGameFile(file_path) == 0) ? STR_VERIFICATION_SUCCESS : STR_VERIFICATION_FAILED); ShowPrompt(false, "%s\n%s", pathstr, (VerifyGameFile(file_path, false) == 0) ? STR_VERIFICATION_SUCCESS : STR_VERIFICATION_FAILED);
} }
} }
return 0; return 0;
@ -1841,10 +1841,24 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
return 0; return 0;
} }
else if (user_select == verify) { // -> verify game / nand file else if (user_select == verify) { // -> verify game / nand file
if ((n_marked > 1) && ShowPrompt(true, STR_TRY_TO_VERIFY_N_SELECTED_FILES, n_marked)) { bool sig_check = false;
// check signatures?
if (filetype & (GAME_NCSD|GAME_NCCH)) {
optionstr[0] = STR_IGNORE_SIGNATURES;
optionstr[1] = STR_VERIFY_SIGNATURES;
user_select = ShowSelectPrompt(2, optionstr, "%s", STR_USE_SIGNATURE_VERIFICATION);
if (!user_select) return 1;
sig_check = (user_select == 2);
}
// file verification
if (n_marked > 1) {
u32 n_success = 0; u32 n_success = 0;
u32 n_other = 0; u32 n_other = 0;
u32 n_processed = 0; u32 n_processed = 0;
if (!ShowPrompt(true, STR_TRY_TO_VERIFY_N_SELECTED_FILES, n_marked)) // confirmation
return 1;
for (u32 i = 0; i < current_dir->n_entries; i++) { for (u32 i = 0; i < current_dir->n_entries; i++) {
const char* path = current_dir->entry[i].path; const char* path = current_dir->entry[i].path;
if (!current_dir->entry[i].marked) if (!current_dir->entry[i].marked)
@ -1857,7 +1871,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
} }
DrawDirContents(current_dir, (*cursor = i), scroll); DrawDirContents(current_dir, (*cursor = i), scroll);
if ((filetype & IMG_NAND) && (ValidateNandDump(path) == 0)) n_success++; if ((filetype & IMG_NAND) && (ValidateNandDump(path) == 0)) n_success++;
else if (VerifyGameFile(path) == 0) n_success++; else if (VerifyGameFile(path, sig_check) == 0) n_success++;
else { // on failure: show error, continue else { // on failure: show error, continue
char lpathstr[UTF_BUFFER_BYTESIZE(32)]; char lpathstr[UTF_BUFFER_BYTESIZE(32)];
TruncateString(lpathstr, path, 32, 8); TruncateString(lpathstr, path, 32, 8);
@ -1876,7 +1890,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
ShowString("%s\n%s", pathstr, STR_VERIFYING_FILE_PLEASE_WAIT); ShowString("%s\n%s", pathstr, STR_VERIFYING_FILE_PLEASE_WAIT);
if (filetype & IMG_NAND) { if (filetype & IMG_NAND) {
ShowPrompt(false, "%s\n%s", pathstr, (ValidateNandDump(file_path) == 0) ? STR_NAND_VALIDATION_SUCCESS : STR_NAND_VALIDATION_FAILED); ShowPrompt(false, "%s\n%s", pathstr, (ValidateNandDump(file_path) == 0) ? STR_NAND_VALIDATION_SUCCESS : STR_NAND_VALIDATION_FAILED);
} else ShowPrompt(false, "%s\n%s", pathstr, (VerifyGameFile(file_path) == 0) ? STR_VERIFICATION_SUCCESS : STR_VERIFICATION_FAILED); } else ShowPrompt(false, "%s\n%s", pathstr, (VerifyGameFile(file_path, sig_check) == 0) ? STR_VERIFICATION_SUCCESS : STR_VERIFICATION_FAILED);
} }
return 0; return 0;
} }

View File

@ -624,7 +624,7 @@ static int internalfs_verify(lua_State* L) {
u64 filetype = IdentifyFileType(path); u64 filetype = IdentifyFileType(path);
if (filetype & IMG_NAND) res = (ValidateNandDump(path) == 0); if (filetype & IMG_NAND) res = (ValidateNandDump(path) == 0);
else res = (VerifyGameFile(path) == 0); else res = (VerifyGameFile(path, false) == 0);
lua_pushboolean(L, res); lua_pushboolean(L, res);
return 1; return 1;

View File

@ -162,6 +162,8 @@ typedef struct _Arm9Itcm {
// Sanity checking. // Sanity checking.
STATIC_ASSERT(offsetof(Arm9Itcm, otp) == 0x3800); STATIC_ASSERT(offsetof(Arm9Itcm, otp) == 0x3800);
STATIC_ASSERT(offsetof(Arm9Itcm, rsaModulusCartNCSD) == 0x4900);
STATIC_ASSERT(offsetof(Arm9Itcm, rsaModulusAccessDesc) == 0x4A00);
STATIC_ASSERT(offsetof(Arm9Itcm, twlNANDKeyY) == 0x53C8); STATIC_ASSERT(offsetof(Arm9Itcm, twlNANDKeyY) == 0x53C8);
STATIC_ASSERT(offsetof(Arm9Itcm, twlBlowfishCartKey) == 0x53E0); STATIC_ASSERT(offsetof(Arm9Itcm, twlBlowfishCartKey) == 0x53E0);
STATIC_ASSERT(offsetof(Arm9Itcm, ntrBlowfishCartKey) == 0x6428); STATIC_ASSERT(offsetof(Arm9Itcm, ntrBlowfishCartKey) == 0x6428);

View File

@ -519,7 +519,7 @@ u32 VerifyTmdContent(const char* path, u64 offset, TmdContentChunk* chunk, const
return memcmp(hash, expected, 32); return memcmp(hash, expected, 32);
} }
u32 VerifyNcchFile(const char* path, u32 offset, u32 size) { u32 VerifyNcchFile(const char* path, u32 offset, u32 size, bool sig_check) {
static bool cryptofix_always = false; static bool cryptofix_always = false;
bool cryptofix = false; bool cryptofix = false;
NcchHeader ncch; NcchHeader ncch;
@ -583,6 +583,13 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) {
return 1; return 1;
} }
// signature verification
if (sig_check && ValidateNcchSignature(&ncch, ncch.size_exthdr ? &exthdr : NULL) != 0) {
if (!offset) ShowPrompt(false, "%s\n%s", pathstr, STR_ERROR_SIGNATURE_CHECK_FAILED);
fvx_close(&file);
return 1;
}
// check / setup crypto // check / setup crypto
if (SetupNcchCrypto(&ncch, NCCH_NOCRYPTO) != 0) { if (SetupNcchCrypto(&ncch, NCCH_NOCRYPTO) != 0) {
if (!offset) ShowPrompt(false, "%s\n%s", pathstr, STR_ERROR_CRYPTO_NOT_SET_UP); if (!offset) ShowPrompt(false, "%s\n%s", pathstr, STR_ERROR_CRYPTO_NOT_SET_UP);
@ -723,7 +730,7 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) {
return ver_exthdr|ver_exefs|ver_romfs; return ver_exthdr|ver_exefs|ver_romfs;
} }
u32 VerifyNcsdFile(const char* path) { u32 VerifyNcsdFile(const char* path, bool sig_check) {
NcsdHeader ncsd; NcsdHeader ncsd;
// path string // path string
@ -736,13 +743,19 @@ u32 VerifyNcsdFile(const char* path) {
return 1; return 1;
} }
// signature verification
if (sig_check && ValidateNcsdSignature(&ncsd) != 0) {
ShowPrompt(false, "%s\n%s", pathstr, STR_ERROR_SIGNATURE_CHECK_FAILED);
return 1;
}
// validate NCSD contents // validate NCSD contents
for (u32 i = 0; i < 8; i++) { for (u32 i = 0; i < 8; i++) {
NcchPartition* partition = ncsd.partitions + i; NcchPartition* partition = ncsd.partitions + i;
u32 offset = partition->offset * NCSD_MEDIA_UNIT; u32 offset = partition->offset * NCSD_MEDIA_UNIT;
u32 size = partition->size * NCSD_MEDIA_UNIT; u32 size = partition->size * NCSD_MEDIA_UNIT;
if (!size) continue; if (!size) continue;
if (VerifyNcchFile(path, offset, size) != 0) { if (VerifyNcchFile(path, offset, size, sig_check) != 0) {
ShowPrompt(false, STR_PATH_CONTENT_N_SIZE_AT_OFFSET_VERIFICATION_FAILED, ShowPrompt(false, STR_PATH_CONTENT_N_SIZE_AT_OFFSET_VERIFICATION_FAILED,
pathstr, i, size, offset); pathstr, i, size, offset);
return 1; return 1;
@ -1032,14 +1045,14 @@ u32 VerifyTicketFile(const char* path) {
return res; return res;
} }
u32 VerifyGameFile(const char* path) { u32 VerifyGameFile(const char* path, bool sig_check) {
u64 filetype = IdentifyFileType(path); u64 filetype = IdentifyFileType(path);
if (filetype & GAME_CIA) if (filetype & GAME_CIA)
return VerifyCiaFile(path); return VerifyCiaFile(path);
else if (filetype & GAME_NCSD) else if (filetype & GAME_NCSD)
return VerifyNcsdFile(path); return VerifyNcsdFile(path, sig_check);
else if (filetype & GAME_NCCH) else if (filetype & GAME_NCCH)
return VerifyNcchFile(path, 0, 0); return VerifyNcchFile(path, 0, 0, sig_check);
else if (filetype & (GAME_TMD|GAME_CDNTMD|GAME_TWLTMD)) else if (filetype & (GAME_TMD|GAME_CDNTMD|GAME_TWLTMD))
return VerifyTmdFile(path, filetype & (GAME_CDNTMD|GAME_TWLTMD)); return VerifyTmdFile(path, filetype & (GAME_CDNTMD|GAME_TWLTMD));
else if (filetype & GAME_TIE) else if (filetype & GAME_TIE)
@ -3617,7 +3630,7 @@ u32 ShowGameCheckerInfo(const char* path) {
(state_tmd == 0) ? STR_STATE_INVALID : (state_tmd == 2) ? STR_STATE_LEGIT : STR_STATE_ILLEGIT, (state_tmd == 0) ? STR_STATE_INVALID : (state_tmd == 2) ? STR_STATE_LEGIT : STR_STATE_ILLEGIT,
(state_verify < 0) ? STR_STATE_PENDING_PROCEED_WITH_VERIFICATION : (state_verify == 0) ? STR_STATE_PASSED : STR_STATE_FAILED) || (state_verify < 0) ? STR_STATE_PENDING_PROCEED_WITH_VERIFICATION : (state_verify == 0) ? STR_STATE_PASSED : STR_STATE_FAILED) ||
(state_verify >= 0)) break; (state_verify >= 0)) break;
state_verify = VerifyGameFile(path); state_verify = VerifyGameFile(path, false);
} }
if (tmd) free(tmd); if (tmd) free(tmd);

View File

@ -2,7 +2,7 @@
#include "common.h" #include "common.h"
u32 VerifyGameFile(const char* path); u32 VerifyGameFile(const char* path, bool sig_check);
u32 CheckEncryptedGameFile(const char* path); u32 CheckEncryptedGameFile(const char* path);
u32 CryptGameFile(const char* path, bool inplace, bool encrypt); u32 CryptGameFile(const char* path, bool inplace, bool encrypt);
u32 BuildCiaFromGameFile(const char* path, bool force_legit); u32 BuildCiaFromGameFile(const char* path, bool force_legit);

View File

@ -1346,7 +1346,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
else if (id == CMD_ID_VERIFY) { else if (id == CMD_ID_VERIFY) {
u64 filetype = IdentifyFileType(argv[0]); u64 filetype = IdentifyFileType(argv[0]);
if (filetype & IMG_NAND) ret = (ValidateNandDump(argv[0]) == 0); if (filetype & IMG_NAND) ret = (ValidateNandDump(argv[0]) == 0);
else ret = (VerifyGameFile(argv[0]) == 0); else ret = (VerifyGameFile(argv[0], false) == 0);
if (err_str) snprintf(err_str, _ERR_STR_LEN, "%s", STR_VERIFICATION_FAILED); if (err_str) snprintf(err_str, _ERR_STR_LEN, "%s", STR_VERIFICATION_FAILED);
} }
else if (id == CMD_ID_DECRYPT) { else if (id == CMD_ID_DECRYPT) {

View File

@ -812,5 +812,9 @@
"SYSINFO_SYSTEM_ID0": "System ID0: %s\r\n", "SYSINFO_SYSTEM_ID0": "System ID0: %s\r\n",
"SYSINFO_SYSTEM_ID1": "System ID1: %s\r\n", "SYSINFO_SYSTEM_ID1": "System ID1: %s\r\n",
"SORTING_TICKETS_PLEASE_WAIT": "Sorting tickets, please wait ...", "SORTING_TICKETS_PLEASE_WAIT": "Sorting tickets, please wait ...",
"LUA_NOT_INCLUDED": "This build of GodMode9 was\ncompiled without Lua support." "LUA_NOT_INCLUDED": "This build of GodMode9 was\ncompiled without Lua support.",
"ERROR_SIGNATURE_CHECK_FAILED": "Error: Signature check failed",
"USE_SIGNATURE_VERIFICATION": "Use signature verification?",
"IGNORE_SIGNATURES": "Ignore signatures",
"VERIFY_SIGNATURES": "Verify signatures"
} }