diff --git a/code_templates/nxdt_rw_poc.c b/code_templates/nxdt_rw_poc.c index f711f12..e4ed89b 100644 --- a/code_templates/nxdt_rw_poc.c +++ b/code_templates/nxdt_rw_poc.c @@ -4533,7 +4533,7 @@ static void xciReadThreadFunc(void *arg) } /* Remove certificate */ - if (!keep_certificate && offset == 0) memset((u8*)buf1 + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate)); + if (!keep_certificate && offset == 0) memset((u8*)buf1 + GAMECARD_CERT_OFFSET, 0xFF, sizeof(FsGameCardCertificate)); /* Update checksum */ if (calculate_checksum) diff --git a/include/core/fs_ext.h b/include/core/fs_ext.h index 8d7c387..c46b3ab 100644 --- a/include/core/fs_ext.h +++ b/include/core/fs_ext.h @@ -32,7 +32,7 @@ extern "C" { /// Located at offset 0x7000 in the gamecard image. typedef struct { - u8 signature[0x100]; ///< RSA-2048-PSS with SHA-256 signature over the rest of the data. + u8 signature[0x100]; ///< RSA-2048-PKCS#1 v1.5 with SHA-256 signature over the rest of the data. u32 magic; ///< "CERT". u32 version; u8 kek_index; diff --git a/include/core/gamecard.h b/include/core/gamecard.h index 6e11bc7..7befe18 100644 --- a/include/core/gamecard.h +++ b/include/core/gamecard.h @@ -31,14 +31,17 @@ extern "C" { #endif -#define GAMECARD_HEAD_MAGIC 0x48454144 /* "HEAD". */ +#define GAMECARD_HEAD_MAGIC 0x48454144 /* "HEAD". */ -#define GAMECARD_PAGE_SIZE 0x200 -#define GAMECARD_PAGE_OFFSET(x) ((u64)(x) * GAMECARD_PAGE_SIZE) +#define GAMECARD_PAGE_SIZE 0x200 +#define GAMECARD_PAGE_OFFSET(x) ((u64)(x) * GAMECARD_PAGE_SIZE) -#define GAMECARD_UPDATE_TID SYSTEM_UPDATE_TID +#define GAMECARD_UPDATE_TID SYSTEM_UPDATE_TID -#define GAMECARD_CERTIFICATE_OFFSET 0x7000 +#define GAMECARD_HEADER2_OFFSET 0x200 +#define GAMECARD_HEADER2_CERT_OFFSET 0x400 + +#define GAMECARD_CERT_OFFSET 0x7000 /// Encrypted using AES-128-ECB with the common titlekek generator key (stored in the .rodata segment from the Lotus firmware). typedef struct { @@ -150,6 +153,14 @@ typedef enum { GameCardFlags_Count = 6 ///< Total values supported by this enum. } GameCardFlags; +/// Available in HOS 18.0.0+. +typedef enum { + GameCardFlags2_None = 0, + GameCardFlags2_T1 = 1, + GameCardFlags2_T2 = 2, + GameCardFlags2_Count = 3 ///< Total values supported by this enum. +} GameCardFlags2; + typedef enum { GameCardSelSec_ForT1 = 1, GameCardSelSec_ForT2 = 2, @@ -198,7 +209,7 @@ NXDT_ASSERT(GameCardInfo, 0x70); /// Placed after the `GameCardKeyArea` section. typedef struct { - u8 signature[0x100]; ///< RSA-2048-PSS with SHA-256 signature over the rest of the header. + u8 signature[0x100]; ///< RSA-2048-PKCS#1 v1.5 with SHA-256 signature over the rest of the header. u32 magic; ///< "HEAD". u32 rom_area_start_page; ///< Expressed in GAMECARD_PAGE_SIZE units. u32 backup_area_start_page; ///< Always 0xFFFFFFFF. @@ -208,7 +219,9 @@ typedef struct { u8 flags; ///< GameCardFlags. u8 package_id[0x8]; ///< Used for challenge-response authentication. u32 valid_data_end_page; ///< Expressed in GAMECARD_PAGE_SIZE units. - u8 reserved[0x4]; + u8 reserved_1; + u8 flags_2; ///< GameCardFlags2. + u8 reserved_2[0x2]; u8 card_info_iv[AES_128_KEY_SIZE]; ///< AES-128-CBC IV for the CardInfo area (reversed). u64 partition_fs_header_address; ///< Root Hash File System header offset. u64 partition_fs_header_size; ///< Root Hash File System header size. @@ -223,6 +236,35 @@ typedef struct { NXDT_ASSERT(GameCardHeader, 0x200); +/// Encrypted using AES-128-CBC. +typedef struct { + u8 unknown[0x40]; + u8 header_hash[SHA256_HASH_SIZE]; + u8 reserved[0x10]; +} GameCardHeader2EncryptedData; + +NXDT_ASSERT(GameCardHeader2EncryptedData, 0x70); + +/// Placed immediately after the `GameCardHeader` section. +typedef struct { + u8 signature[0x100]; ///< RSA-2048-PKCS#1 v1.5 with SHA-256 signature over the rest of the header. + u8 unknown[0x90]; + GameCardHeader2EncryptedData encrypted_data; +} GameCardHeader2; + +NXDT_ASSERT(GameCardHeader2, 0x200); + +/// Placed immediately after the `GameCardHeader2` section. +typedef struct { + u8 signature[0x100]; ///< RSA-2048-PKCS#1 v1.5 with SHA-256 signature over the data from 0x100 to 0x300. + u8 unknown_1[0x30]; + u8 modulus[0x100]; ///< RSA modulus used to verify the signature from GameCardHeader2. + u8 exponent[0x4]; ///< RSA exponent used to verify the signature from GameCardHeader2. + u8 unknown_2[0x1CC]; +} GameCardHeader2Certificate; + +NXDT_ASSERT(GameCardHeader2Certificate, 0x400); + typedef enum { GameCardStatus_NotInserted = 0, ///< No gamecard is inserted. GameCardStatus_Processing = 1, ///< A gamecard has been inserted and it's being processed. @@ -310,7 +352,7 @@ bool gamecardGetHeader(GameCardHeader *out); bool gamecardGetPlaintextCardInfoArea(GameCardInfo *out); /// Fills the provided FsGameCardCertificate pointer. -/// This area can also be read using gamecardReadStorage(), starting at GAMECARD_CERTIFICATE_OFFSET. +/// This area can also be read using gamecardReadStorage(), starting at GAMECARD_CERT_OFFSET. bool gamecardGetCertificate(FsGameCardCertificate *out); /// Fills the provided u64 pointer with the total gamecard size, which is the size taken by both Normal and Secure storage areas. diff --git a/source/core/gamecard.c b/source/core/gamecard.c index 7df9314..26a9dd5 100644 --- a/source/core/gamecard.c +++ b/source/core/gamecard.c @@ -23,6 +23,7 @@ #include "mem.h" #include "gamecard.h" #include "keys.h" +#include "rsa.h" #define GAMECARD_READ_BUFFER_SIZE 0x800000 /* 8 MiB. */ @@ -78,6 +79,10 @@ static u8 *g_gameCardReadBuf = NULL; static GameCardHeader g_gameCardHeader = {0}; static GameCardInfo g_gameCardInfoArea = {0}; + +static GameCardHeader2 g_gameCardHeader2 = {0}; +static GameCardHeader2Certificate g_gameCardHeader2Cert = {0}; + static u64 g_gameCardNormalAreaSize = 0, g_gameCardSecureAreaSize = 0, g_gameCardTotalSize = 0; static u64 g_gameCardCapacity = 0; @@ -829,9 +834,11 @@ end: static void gamecardFreeInfo(bool clear_status) { memset(&g_gameCardHeader, 0, sizeof(GameCardHeader)); - memset(&g_gameCardInfoArea, 0, sizeof(GameCardInfo)); + memset(&g_gameCardHeader2, 0, sizeof(GameCardHeader2)); + memset(&g_gameCardHeader2Cert, 0, sizeof(GameCardHeader2Certificate)); + g_gameCardNormalAreaSize = g_gameCardSecureAreaSize = g_gameCardTotalSize = 0; g_gameCardCapacity = 0; @@ -861,6 +868,8 @@ static void gamecardFreeInfo(bool clear_status) static bool gamecardReadHeader(void) { + Result rc = 0; + /* Open normal storage area. */ if (!gamecardOpenStorageArea(GameCardStorageArea_Normal)) { @@ -870,7 +879,7 @@ static bool gamecardReadHeader(void) /* Read gamecard header. */ /* We don't use gamecardReadStorageArea() here because of its dependence on storage area sizes (which we haven't yet retrieved). */ - Result rc = fsStorageRead(&g_gameCardStorage, 0, &g_gameCardHeader, sizeof(GameCardHeader)); + rc = fsStorageRead(&g_gameCardStorage, 0, &g_gameCardHeader, sizeof(GameCardHeader)); if (R_FAILED(rc)) { LOG_MSG_ERROR("fsStorageRead failed to read gamecard header! (0x%X).", rc); @@ -886,6 +895,43 @@ static bool gamecardReadHeader(void) return false; } + /* Check if a Header2 area is available. */ + if (g_gameCardHeader.flags & GameCardFlags_HasCa10Certificate) + { + /* Read the Header2 area. */ + rc = fsStorageRead(&g_gameCardStorage, GAMECARD_HEADER2_OFFSET, &g_gameCardHeader2, sizeof(GameCardHeader2)); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("fsStorageRead failed to read gamecard Header2 area! (0x%X).", rc); + return false; + } + + LOG_DATA_DEBUG(&g_gameCardHeader2, sizeof(GameCardHeader2), "Gamecard Header2 dump:"); + + /* Read the Header2Certificate area. */ + rc = fsStorageRead(&g_gameCardStorage, GAMECARD_HEADER2_CERT_OFFSET, &g_gameCardHeader2Cert, sizeof(GameCardHeader2Certificate)); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("fsStorageRead failed to read gamecard Header2Certificate area! (0x%X).", rc); + return false; + } + + LOG_DATA_DEBUG(&g_gameCardHeader2Cert, sizeof(GameCardHeader2Certificate), "Gamecard Header2Certificate dump:"); + + /* Verify the signature from the Header2 area. */ + if (!rsa2048VerifySha256BasedPkcs1v15Signature(&(g_gameCardHeader2.unknown), sizeof(GameCardHeader2) - MEMBER_SIZE(GameCardHeader2, signature), g_gameCardHeader2.signature, \ + g_gameCardHeader2Cert.modulus, g_gameCardHeader2Cert.exponent, sizeof(g_gameCardHeader2Cert.exponent))) + { + LOG_MSG_ERROR("Gamecard Header2 signature verification failed!"); + return false; + } + + // TODO: remove this once anyone comes across a gamecard with an actual Header2 area. + // Public non-static functions to retrieve both the Header2 and the Header2Certificate areas will be implemented afterwards. + // For the time being, we will force an error. + return false; + } + return true; } @@ -1212,7 +1258,7 @@ static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char bool success = false, dump_fs_header = false; - if ((name && !*name) || offset < (GAMECARD_CERTIFICATE_OFFSET + sizeof(FsGameCardCertificate)) || !IS_ALIGNED(offset, GAMECARD_PAGE_SIZE) || \ + if ((name && !*name) || offset < (GAMECARD_CERT_OFFSET + sizeof(FsGameCardCertificate)) || !IS_ALIGNED(offset, GAMECARD_PAGE_SIZE) || \ (size && (!IS_ALIGNED(size, GAMECARD_PAGE_SIZE) || (offset + size) > g_gameCardTotalSize))) { LOG_MSG_ERROR("Invalid parameters!"); diff --git a/source/gamecard_tab.cpp b/source/gamecard_tab.cpp index e465fad..e5b5766 100644 --- a/source/gamecard_tab.cpp +++ b/source/gamecard_tab.cpp @@ -275,7 +275,7 @@ namespace nxdt::views brls::ListItem *dump_initial_data = new brls::ListItem("gamecard_tab/list/dump_initial_data/label"_i18n, "gamecard_tab/list/dump_initial_data/description"_i18n); this->list->addView(dump_initial_data); - brls::ListItem *dump_certificate = new brls::ListItem("gamecard_tab/list/dump_certificate/label"_i18n, i18n::getStr("gamecard_tab/list/dump_certificate/description", GAMECARD_CERTIFICATE_OFFSET / GAMECARD_PAGE_SIZE)); + brls::ListItem *dump_certificate = new brls::ListItem("gamecard_tab/list/dump_certificate/label"_i18n, i18n::getStr("gamecard_tab/list/dump_certificate/description", GAMECARD_CERT_OFFSET / GAMECARD_PAGE_SIZE)); this->list->addView(dump_certificate); brls::ListItem *dump_card_id_set = new brls::ListItem("gamecard_tab/list/dump_card_id_set/label"_i18n, "gamecard_tab/list/dump_card_id_set/description"_i18n);