gamecard: add support for Header2-related areas

These changes include:

* gamecard: add GAMECARD_HEADER2_OFFSET and GAMECARD_HEADER2_CERT_OFFSET macros.
* gamecard: rename GAMECARD_CERTIFICATE_OFFSET macro to GAMECARD_CERT_OFFSET.
* gamecard: add GameCardFlags2 enum.
* gamecard: update GameCardHeader struct to reflect the new Flags2 field.
* gamecard: add GameCardHeader2, GameCardHeader2EncryptedData and GameCardHeader2Certificate structs.
* gamecard: update gamecardReadHeader() to check if the `Has10Certificate` flag is set in the gamecard header. If it is, the function will read and log both Header2 and Header2Certificate areas, and it will also attempt to verify the RSA signature from the Header2 area.
* gamecard: if a Header2 area is detected, an error will always be triggered for the time being. If anyone comes across this error, please get in touch me.

* gamecard, fs_ext: fix signature field comments.
This commit is contained in:
Pablo Curiel 2024-04-19 15:58:16 +02:00
parent 6dfbb301df
commit 4af144486a
5 changed files with 102 additions and 14 deletions

View File

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

View File

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

View File

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

View File

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

View File

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