mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-12-23 00:52:10 +00:00
LAFW shenanigans.
* Added custom key sources to derive CardInfo keys at runtime using SPL. * Implemented CardInfo area decryption. * Implemented LAFW blob lookup in FS .data segment to retrieve the current LAFW version. P.S.: still need to move around code to perform the LAFW version check at the places we need. But the current code is good enough for a test.
This commit is contained in:
parent
0ce6d244e5
commit
21d1b001ee
6 changed files with 290 additions and 27 deletions
|
@ -126,7 +126,8 @@ typedef enum {
|
||||||
GameCardFwVersion_ForProdSince400NUP = 2, ///< upp_version >= 268435456 (4.0.0-0.0) in GameCardInfo.
|
GameCardFwVersion_ForProdSince400NUP = 2, ///< upp_version >= 268435456 (4.0.0-0.0) in GameCardInfo.
|
||||||
GameCardFwVersion_ForDevSince1100NUP = 3, ///< upp_version >= 738197504 (11.0.0-0.0) in GameCardInfo.
|
GameCardFwVersion_ForDevSince1100NUP = 3, ///< upp_version >= 738197504 (11.0.0-0.0) in GameCardInfo.
|
||||||
GameCardFwVersion_ForProdSince1100NUP = 4, ///< upp_version >= 738197504 (11.0.0-0.0) in GameCardInfo.
|
GameCardFwVersion_ForProdSince1100NUP = 4, ///< upp_version >= 738197504 (11.0.0-0.0) in GameCardInfo.
|
||||||
GameCardFwVersion_ForProdSince1200NUP = 5 ///< upp_version >= 805306368 (12.0.0-0.0) in GameCardInfo.
|
GameCardFwVersion_ForProdSince1200NUP = 5, ///< upp_version >= 805306368 (12.0.0-0.0) in GameCardInfo.
|
||||||
|
GameCardFwVersion_Count = 6
|
||||||
} GameCardFwVersion;
|
} GameCardFwVersion;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
@ -139,7 +140,7 @@ typedef enum {
|
||||||
GameCardCompatibilityType_Terra = 1
|
GameCardCompatibilityType_Terra = 1
|
||||||
} GameCardCompatibilityType;
|
} GameCardCompatibilityType;
|
||||||
|
|
||||||
/// Encrypted using AES-128-CBC with the XCI header key (found in FS program memory under newer versions of HOS) and the IV from `GameCardHeader`.
|
/// Encrypted using AES-128-CBC with the XCI header key (found in FS program memory under HOS 9.0.0+) and the IV from `GameCardHeader`.
|
||||||
/// Key hashes for documentation purposes:
|
/// Key hashes for documentation purposes:
|
||||||
/// Production XCI header key hash: 2E36CC55157A351090A73E7AE77CF581F69B0B6E48FB066C984879A6ED7D2E96
|
/// Production XCI header key hash: 2E36CC55157A351090A73E7AE77CF581F69B0B6E48FB066C984879A6ED7D2E96
|
||||||
/// Development XCI header key hash: 61D5C02244188810E2E3DE69341AC0F3C7653D370C6D3F77CA82B0B7E59F39AD
|
/// Development XCI header key hash: 61D5C02244188810E2E3DE69341AC0F3C7653D370C6D3F77CA82B0B7E59F39AD
|
||||||
|
@ -174,7 +175,7 @@ typedef struct {
|
||||||
u64 package_id; ///< Used for challenge–response authentication.
|
u64 package_id; ///< Used for challenge–response authentication.
|
||||||
u32 valid_data_end_address; ///< Expressed in GAMECARD_PAGE_SIZE units.
|
u32 valid_data_end_address; ///< Expressed in GAMECARD_PAGE_SIZE units.
|
||||||
u8 reserved[0x4];
|
u8 reserved[0x4];
|
||||||
u8 iv[0x10];
|
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_address; ///< Root Hash File System header offset.
|
||||||
u64 partition_fs_header_size; ///< Root Hash File System header size.
|
u64 partition_fs_header_size; ///< Root Hash File System header size.
|
||||||
u8 partition_fs_header_hash[SHA256_HASH_SIZE];
|
u8 partition_fs_header_hash[SHA256_HASH_SIZE];
|
||||||
|
|
|
@ -30,9 +30,9 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Loads (and derives) NCA keydata from sysmodule program memory and the Lockpick_RCM keys file.
|
/// Loads (and derives) keydata from sysmodule program memory, the Lockpick_RCM keys file and hardcoded/obfuscated information.
|
||||||
/// Must be called (and succeed) before calling any of the functions below.
|
/// Must be called (and succeed) before calling any of the functions below.
|
||||||
bool keysLoadNcaKeyset(void);
|
bool keysLoadKeyset(void);
|
||||||
|
|
||||||
/// Returns a pointer to the AES-128-XTS NCA header key, or NULL if keydata hasn't been loaded.
|
/// Returns a pointer to the AES-128-XTS NCA header key, or NULL if keydata hasn't been loaded.
|
||||||
const u8 *keysGetNcaHeaderKey(void);
|
const u8 *keysGetNcaHeaderKey(void);
|
||||||
|
@ -57,6 +57,9 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o
|
||||||
/// Returns a pointer to an AES-128-ECB ticket common key using the provided key generation value, or NULL if keydata hasn't been loaded.
|
/// Returns a pointer to an AES-128-ECB ticket common key using the provided key generation value, or NULL if keydata hasn't been loaded.
|
||||||
const u8 *keysGetTicketCommonKey(u8 key_generation);
|
const u8 *keysGetTicketCommonKey(u8 key_generation);
|
||||||
|
|
||||||
|
/// Returns a pointer to the AES-128-CBC CardInfo area key for gamecard headers, or NULL if keydata hasn't been loaded.
|
||||||
|
const u8 *keysGetGameCardInfoKey(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "nxdt_utils.h"
|
#include "nxdt_utils.h"
|
||||||
#include "mem.h"
|
#include "mem.h"
|
||||||
#include "gamecard.h"
|
#include "gamecard.h"
|
||||||
|
#include "keys.h"
|
||||||
|
|
||||||
#define GAMECARD_HFS0_MAGIC 0x48465330 /* "HFS0". */
|
#define GAMECARD_HFS0_MAGIC 0x48465330 /* "HFS0". */
|
||||||
|
|
||||||
|
@ -34,6 +35,8 @@
|
||||||
|
|
||||||
#define GAMECARD_STORAGE_AREA_NAME(x) ((x) == GameCardStorageArea_Normal ? "normal" : ((x) == GameCardStorageArea_Secure ? "secure" : "none"))
|
#define GAMECARD_STORAGE_AREA_NAME(x) ((x) == GameCardStorageArea_Normal ? "normal" : ((x) == GameCardStorageArea_Secure ? "secure" : "none"))
|
||||||
|
|
||||||
|
#define LAFW_MAGIC 0x4C414657 /* "LAFW". */
|
||||||
|
|
||||||
/* Type definitions. */
|
/* Type definitions. */
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
@ -64,6 +67,39 @@ typedef struct {
|
||||||
|
|
||||||
NXDT_ASSERT(GameCardSecurityInformation, 0x600);
|
NXDT_ASSERT(GameCardSecurityInformation, 0x600);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LotusAsicFirmwareType_ReadFw = 0xFF,
|
||||||
|
LotusAsicFirmwareType_ReadDevFw = 0xFFFF,
|
||||||
|
LotusAsicFirmwareType_WriterFw = 0xFFFFFF,
|
||||||
|
LotusAsicFirmwareType_RmaFw = 0xFFFFFFFF
|
||||||
|
} LotusAsicFirmwareType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LotusAsicDeviceType_Test = 0,
|
||||||
|
LotusAsicDeviceType_Dev = 1,
|
||||||
|
LotusAsicDeviceType_Prod = 2,
|
||||||
|
LotusAsicDeviceType_Prod2Dev = 3
|
||||||
|
} LotusAsicDeviceType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u8 signature[0x100];
|
||||||
|
u32 magic; ///< "LAFW".
|
||||||
|
u32 fw_type; ///< LotusAsicFirmwareType.
|
||||||
|
u8 reserved_1[0x8];
|
||||||
|
struct {
|
||||||
|
u64 fw_version : 62; ///< Stored using a bitmask.
|
||||||
|
u64 device_type : 2; ///< LotusAsicDeviceType.
|
||||||
|
};
|
||||||
|
u32 data_size;
|
||||||
|
u8 reserved_2[0x4];
|
||||||
|
u8 data_iv[AES_128_KEY_SIZE];
|
||||||
|
char placeholder_str[0x10]; ///< "IDIDIDIDIDIDIDID".
|
||||||
|
u8 reserved_3[0x40];
|
||||||
|
u8 data[0x7680];
|
||||||
|
} LotusAsicFirmwareBlob;
|
||||||
|
|
||||||
|
NXDT_ASSERT(LotusAsicFirmwareBlob, 0x7800);
|
||||||
|
|
||||||
/* Global variables. */
|
/* Global variables. */
|
||||||
|
|
||||||
static Mutex g_gameCardMutex = 0;
|
static Mutex g_gameCardMutex = 0;
|
||||||
|
@ -74,6 +110,8 @@ static FsEventNotifier g_gameCardEventNotifier = {0};
|
||||||
static Event g_gameCardKernelEvent = {0};
|
static Event g_gameCardKernelEvent = {0};
|
||||||
static bool g_openDeviceOperator = false, g_openEventNotifier = false, g_loadKernelEvent = false;
|
static bool g_openDeviceOperator = false, g_openEventNotifier = false, g_loadKernelEvent = false;
|
||||||
|
|
||||||
|
static u64 g_lafwVersion = 0;
|
||||||
|
|
||||||
static Thread g_gameCardDetectionThread = {0};
|
static Thread g_gameCardDetectionThread = {0};
|
||||||
static UEvent g_gameCardDetectionThreadExitEvent = {0}, g_gameCardStatusChangeEvent = {0};
|
static UEvent g_gameCardDetectionThreadExitEvent = {0}, g_gameCardStatusChangeEvent = {0};
|
||||||
static bool g_gameCardDetectionThreadCreated = false, g_gameCardInserted = false, g_gameCardInfoLoaded = false;
|
static bool g_gameCardDetectionThreadCreated = false, g_gameCardInserted = false, g_gameCardInfoLoaded = false;
|
||||||
|
@ -84,6 +122,7 @@ static u8 g_gameCardStorageCurrentArea = GameCardStorageArea_None;
|
||||||
static u8 *g_gameCardReadBuf = NULL;
|
static u8 *g_gameCardReadBuf = NULL;
|
||||||
|
|
||||||
static GameCardHeader g_gameCardHeader = {0};
|
static GameCardHeader g_gameCardHeader = {0};
|
||||||
|
static GameCardInfo g_gameCardInfoArea = {0};
|
||||||
static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0, g_gameCardStorageTotalSize = 0;
|
static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0, g_gameCardStorageTotalSize = 0;
|
||||||
static u64 g_gameCardCapacity = 0;
|
static u64 g_gameCardCapacity = 0;
|
||||||
|
|
||||||
|
@ -108,6 +147,8 @@ static const char *g_gameCardHfsPartitionNames[] = {
|
||||||
|
|
||||||
/* Function prototypes. */
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static bool gamecardGetLotusAsicFirmwareVersion(void);
|
||||||
|
|
||||||
static bool gamecardCreateDetectionThread(void);
|
static bool gamecardCreateDetectionThread(void);
|
||||||
static void gamecardDestroyDetectionThread(void);
|
static void gamecardDestroyDetectionThread(void);
|
||||||
static void gamecardDetectionThreadFunc(void *arg);
|
static void gamecardDetectionThreadFunc(void *arg);
|
||||||
|
@ -129,6 +170,8 @@ static void gamecardCloseStorageArea(void);
|
||||||
static bool gamecardGetStorageAreasSizes(void);
|
static bool gamecardGetStorageAreasSizes(void);
|
||||||
NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size);
|
NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size);
|
||||||
|
|
||||||
|
static bool gamecardGetDecryptedCardInfoArea(void);
|
||||||
|
|
||||||
static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char *name, u64 offset, u64 size, u8 *hash, u64 hash_target_offset, u32 hash_target_size);
|
static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char *name, u64 offset, u64 size, u8 *hash, u64 hash_target_offset, u32 hash_target_size);
|
||||||
static HashFileSystemContext *_gamecardGetHashFileSystemContext(u8 hfs_partition_type);
|
static HashFileSystemContext *_gamecardGetHashFileSystemContext(u8 hfs_partition_type);
|
||||||
|
|
||||||
|
@ -186,6 +229,9 @@ bool gamecardInitialize(void)
|
||||||
/* Create user-mode gamecard status change event. */
|
/* Create user-mode gamecard status change event. */
|
||||||
ueventCreate(&g_gameCardStatusChangeEvent, true);
|
ueventCreate(&g_gameCardStatusChangeEvent, true);
|
||||||
|
|
||||||
|
/* Retrieve LAFW version. */
|
||||||
|
if (!gamecardGetLotusAsicFirmwareVersion()) break;
|
||||||
|
|
||||||
/* Create gamecard detection thread. */
|
/* Create gamecard detection thread. */
|
||||||
if (!(g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread())) break;
|
if (!(g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread())) break;
|
||||||
|
|
||||||
|
@ -459,6 +505,69 @@ bool gamecardGetHashFileSystemEntryInfoByName(u8 hfs_partition_type, const char
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool gamecardGetLotusAsicFirmwareVersion(void)
|
||||||
|
{
|
||||||
|
u64 fw_version = 0;
|
||||||
|
bool ret = false, found = false, dev_unit = utilsIsDevelopmentUnit();
|
||||||
|
|
||||||
|
/* Temporarily set the segment mask to .data. */
|
||||||
|
g_fsProgramMemory.mask = MemoryProgramSegmentType_Data;
|
||||||
|
|
||||||
|
/* Retrieve full FS program memory dump. */
|
||||||
|
ret = memRetrieveProgramMemorySegment(&g_fsProgramMemory);
|
||||||
|
|
||||||
|
/* Clear segment mask. */
|
||||||
|
g_fsProgramMemory.mask = 0;
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
LOG_MSG("Failed to retrieve FS .data segment dump!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look for the LAFW ReadFw blob in the FS .data memory dump. */
|
||||||
|
for(u64 offset = 0; offset < g_fsProgramMemory.data_size; offset++)
|
||||||
|
{
|
||||||
|
if ((g_fsProgramMemory.data_size - offset) < sizeof(LotusAsicFirmwareBlob)) break;
|
||||||
|
|
||||||
|
LotusAsicFirmwareBlob *lafw_blob = (LotusAsicFirmwareBlob*)(g_fsProgramMemory.data + offset);
|
||||||
|
u32 magic = __builtin_bswap32(lafw_blob->magic), fw_type = lafw_blob->fw_type;
|
||||||
|
|
||||||
|
if (magic == LAFW_MAGIC && ((!dev_unit && fw_type == LotusAsicFirmwareType_ReadFw) || (dev_unit && fw_type == LotusAsicFirmwareType_ReadDevFw)))
|
||||||
|
{
|
||||||
|
/* Jackpot. */
|
||||||
|
fw_version = lafw_blob->fw_version;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
LOG_MSG("Unable to locate Lotus ReadFw blob in FS .data segment!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert LAFW version bitmask to an integer. */
|
||||||
|
g_lafwVersion = 0;
|
||||||
|
|
||||||
|
while(fw_version)
|
||||||
|
{
|
||||||
|
g_lafwVersion += (fw_version & 1);
|
||||||
|
fw_version >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_MSG("LAFW version: %lu.", g_lafwVersion);
|
||||||
|
|
||||||
|
/* Update flag. */
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
memFreeMemoryLocation(&g_fsProgramMemory);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static bool gamecardCreateDetectionThread(void)
|
static bool gamecardCreateDetectionThread(void)
|
||||||
{
|
{
|
||||||
if (!utilsCreateThread(&g_gameCardDetectionThread, gamecardDetectionThreadFunc, NULL, 1))
|
if (!utilsCreateThread(&g_gameCardDetectionThread, gamecardDetectionThreadFunc, NULL, 1))
|
||||||
|
@ -577,6 +686,51 @@ static void gamecardLoadInfo(void)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Get decrypted CardInfo area. */
|
||||||
|
if (!gamecardGetDecryptedCardInfoArea()) goto end;
|
||||||
|
|
||||||
|
LOG_DATA(&g_gameCardInfoArea, sizeof(GameCardInfo), "Gamecard CardInfo area dump:");
|
||||||
|
|
||||||
|
/* Check if we meet the Lotus ASIC firmware (LAFW) version requirement. */
|
||||||
|
/* Lotus treats the GameCardFwVersion field as the maximum unsupported LAFW version, instead of treating it as the minimum supported version. */
|
||||||
|
/* TODO: move this check somewhere else. We're supposed to do it only if things go south while reading gamecard data. */
|
||||||
|
if (g_lafwVersion <= g_gameCardInfoArea.fw_version)
|
||||||
|
{
|
||||||
|
LOG_MSG("LAFW version not supported by the inserted gamecard (%lu <= %lu).", g_lafwVersion, g_gameCardInfoArea.fw_version);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Get gamecard capacity. */
|
/* Get gamecard capacity. */
|
||||||
g_gameCardCapacity = gamecardGetCapacityFromRomSizeValue(g_gameCardHeader.rom_size);
|
g_gameCardCapacity = gamecardGetCapacityFromRomSizeValue(g_gameCardHeader.rom_size);
|
||||||
if (!g_gameCardCapacity)
|
if (!g_gameCardCapacity)
|
||||||
|
@ -654,6 +808,8 @@ static void gamecardFreeInfo(void)
|
||||||
{
|
{
|
||||||
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
|
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
|
||||||
|
|
||||||
|
memset(&g_gameCardInfoArea, 0, sizeof(GameCardInfo));
|
||||||
|
|
||||||
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0;
|
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0;
|
||||||
|
|
||||||
g_gameCardCapacity = 0;
|
g_gameCardCapacity = 0;
|
||||||
|
@ -976,6 +1132,39 @@ NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size)
|
||||||
return capacity;
|
return capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool gamecardGetDecryptedCardInfoArea(void)
|
||||||
|
{
|
||||||
|
const u8 *card_info_key = NULL;
|
||||||
|
u8 card_info_iv[AES_128_KEY_SIZE] = {0};
|
||||||
|
Aes128CbcContext aes_ctx = {0};
|
||||||
|
|
||||||
|
/* Retrieve CardInfo area key. */
|
||||||
|
card_info_key = keysGetGameCardInfoKey();
|
||||||
|
if (!card_info_key)
|
||||||
|
{
|
||||||
|
LOG_MSG("Failed to retrieve CardInfo area key!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reverse CardInfo IV. */
|
||||||
|
for(u8 i = 0; i < AES_128_KEY_SIZE; i++) card_info_iv[i] = g_gameCardHeader.card_info_iv[AES_128_KEY_SIZE - i - 1];
|
||||||
|
|
||||||
|
/* Initialize AES-128-CBC context. */
|
||||||
|
aes128CbcContextCreate(&aes_ctx, card_info_key, card_info_iv, false);
|
||||||
|
|
||||||
|
/* Decrypt CardInfo area. */
|
||||||
|
aes128CbcDecrypt(&aes_ctx, &g_gameCardInfoArea, &(g_gameCardHeader.card_info), sizeof(GameCardInfo));
|
||||||
|
|
||||||
|
/* Verify update ID. */
|
||||||
|
if (utilsGetCustomFirmwareType() != UtilsCustomFirmwareType_SXOS && g_gameCardInfoArea.upp_id != GAMECARD_UPDATE_TID)
|
||||||
|
{
|
||||||
|
LOG_MSG("Failed to decrypt CardInfo area!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char *name, u64 offset, u64 size, u8 *hash, u64 hash_target_offset, u32 hash_target_size)
|
static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char *name, u64 offset, u64 size, u8 *hash, u64 hash_target_offset, u32 hash_target_size)
|
||||||
{
|
{
|
||||||
u32 i = 0, magic = 0;
|
u32 i = 0, magic = 0;
|
||||||
|
|
|
@ -73,13 +73,24 @@ typedef struct {
|
||||||
u8 ticket_common_keys[NcaKeyGeneration_Max][AES_128_KEY_SIZE]; ///< Retrieved from the Lockpick_RCM keys file.
|
u8 ticket_common_keys[NcaKeyGeneration_Max][AES_128_KEY_SIZE]; ///< Retrieved from the Lockpick_RCM keys file.
|
||||||
} KeysNcaKeyset;
|
} KeysNcaKeyset;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/// AES-128-CBC keys needed to decrypt the CardInfo area from gamecard headers.
|
||||||
|
const u8 gc_cardinfo_kek_source[AES_128_KEY_SIZE]; ///< Randomly generated KEK source to decrypt official CardInfo area keys.
|
||||||
|
const u8 gc_cardinfo_key_prod_source[AES_128_KEY_SIZE]; ///< CardInfo area key used in retail units. Obfuscated using the above KEK source and SMC AES engine keydata.
|
||||||
|
const u8 gc_cardinfo_key_dev_source[AES_128_KEY_SIZE]; ///< CardInfo area key used in development units. Obfuscated using the above KEK source and SMC AES engine keydata.
|
||||||
|
|
||||||
|
u8 gc_cardinfo_kek_sealed[AES_128_KEY_SIZE]; ///< Generated from gc_cardinfo_kek_source. Sealed by the SMC AES engine.
|
||||||
|
u8 gc_cardinfo_key_prod[AES_128_KEY_SIZE]; ///< Generated from gc_cardinfo_kek_sealed and gc_cardinfo_key_prod_source.
|
||||||
|
u8 gc_cardinfo_key_dev[AES_128_KEY_SIZE]; ///< Generated from gc_cardinfo_kek_sealed and gc_cardinfo_key_dev_source.
|
||||||
|
} KeysGameCardKeyset;
|
||||||
|
|
||||||
/// Used to parse the eTicket RSA device key retrieved from PRODINFO via setcalGetEticketDeviceKey().
|
/// Used to parse the eTicket RSA device key retrieved from PRODINFO via setcalGetEticketDeviceKey().
|
||||||
/// Everything after the AES CTR is encrypted using the eTicket RSA device key encryption key.
|
/// Everything after the AES CTR is encrypted using the eTicket RSA device key encryption key.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u8 ctr[AES_128_KEY_SIZE];
|
u8 ctr[AES_128_KEY_SIZE];
|
||||||
u8 private_exponent[RSA2048_BYTES];
|
u8 private_exponent[RSA2048_BYTES];
|
||||||
u8 modulus[RSA2048_BYTES];
|
u8 modulus[RSA2048_BYTES];
|
||||||
u32 public_exponent; ///< Must match ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT. Stored using big endian byte order.
|
u32 public_exponent; ///< Stored using big endian byte order. Must match ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT.
|
||||||
u8 padding[0x14];
|
u8 padding[0x14];
|
||||||
u64 device_id;
|
u64 device_id;
|
||||||
u8 ghash[0x10];
|
u8 ghash[0x10];
|
||||||
|
@ -108,11 +119,23 @@ static bool keysReadKeysFromFile(void);
|
||||||
static bool keysGetDecryptedEticketRsaDeviceKey(void);
|
static bool keysGetDecryptedEticketRsaDeviceKey(void);
|
||||||
static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void *n);
|
static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void *n);
|
||||||
|
|
||||||
|
static bool keysDeriveGameCardKeys(void);
|
||||||
|
|
||||||
/* Global variables. */
|
/* Global variables. */
|
||||||
|
|
||||||
static KeysNcaKeyset g_ncaKeyset = {0};
|
static KeysNcaKeyset g_ncaKeyset = {0};
|
||||||
static bool g_ncaKeysetLoaded = false;
|
|
||||||
static Mutex g_ncaKeysetMutex = 0;
|
static KeysGameCardKeyset g_gameCardKeyset = {
|
||||||
|
.gc_cardinfo_kek_source = { 0xDE, 0xC6, 0x3F, 0x6A, 0xBF, 0x37, 0x72, 0x0B, 0x7E, 0x54, 0x67, 0x6A, 0x2D, 0xEF, 0xDD, 0x97 },
|
||||||
|
.gc_cardinfo_key_prod_source = { 0xF4, 0x92, 0x06, 0x52, 0xD6, 0x37, 0x70, 0xAF, 0xB1, 0x9C, 0x6F, 0x63, 0x09, 0x01, 0xF6, 0x29 },
|
||||||
|
.gc_cardinfo_key_dev_source = { 0x0B, 0x7D, 0xBB, 0x2C, 0xCF, 0x64, 0x1A, 0xF4, 0xD7, 0x38, 0x81, 0x3F, 0x0C, 0x33, 0xF4, 0x1C },
|
||||||
|
.gc_cardinfo_kek_sealed = {0},
|
||||||
|
.gc_cardinfo_key_prod = {0},
|
||||||
|
.gc_cardinfo_key_dev = {0}
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool g_keysetLoaded = false;
|
||||||
|
static Mutex g_keysetMutex = 0;
|
||||||
|
|
||||||
static SetCalRsa2048DeviceKey g_eTicketRsaDeviceKey = {0};
|
static SetCalRsa2048DeviceKey g_eTicketRsaDeviceKey = {0};
|
||||||
|
|
||||||
|
@ -230,13 +253,13 @@ static KeysMemoryInfo g_fsDataMemoryInfo = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
bool keysLoadNcaKeyset(void)
|
bool keysLoadKeyset(void)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
SCOPED_LOCK(&g_ncaKeysetMutex)
|
SCOPED_LOCK(&g_keysetMutex)
|
||||||
{
|
{
|
||||||
ret = g_ncaKeysetLoaded;
|
ret = g_keysetLoaded;
|
||||||
if (ret) break;
|
if (ret) break;
|
||||||
|
|
||||||
/* Retrieve FS .rodata keys. */
|
/* Retrieve FS .rodata keys. */
|
||||||
|
@ -273,14 +296,18 @@ bool keysLoadNcaKeyset(void)
|
||||||
/* Get decrypted eTicket RSA device key. */
|
/* Get decrypted eTicket RSA device key. */
|
||||||
if (!keysGetDecryptedEticketRsaDeviceKey()) break;
|
if (!keysGetDecryptedEticketRsaDeviceKey()) break;
|
||||||
|
|
||||||
|
/* Derive gamecard keys. */
|
||||||
|
if (!keysDeriveGameCardKeys()) break;
|
||||||
|
|
||||||
/* Update flags. */
|
/* Update flags. */
|
||||||
ret = g_ncaKeysetLoaded = true;
|
ret = g_keysetLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if (ret)
|
/*if (ret)
|
||||||
{
|
{
|
||||||
LOG_DATA(&g_ncaKeyset, sizeof(KeysNcaKeyset), "NCA keyset dump:");
|
LOG_DATA(&g_ncaKeyset, sizeof(KeysNcaKeyset), "NCA keyset dump:");
|
||||||
LOG_DATA(&g_eTicketRsaDeviceKey, sizeof(SetCalRsa2048DeviceKey), "eTicket RSA device key dump:");
|
LOG_DATA(&g_eTicketRsaDeviceKey, sizeof(SetCalRsa2048DeviceKey), "eTicket RSA device key dump:");
|
||||||
|
LOG_DATA(&g_gameCardKeyset, sizeof(KeysGameCardKeyset), "Gamecard keyset dump:");
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -290,9 +317,9 @@ const u8 *keysGetNcaHeaderKey(void)
|
||||||
{
|
{
|
||||||
const u8 *ret = NULL;
|
const u8 *ret = NULL;
|
||||||
|
|
||||||
SCOPED_LOCK(&g_ncaKeysetMutex)
|
SCOPED_LOCK(&g_keysetMutex)
|
||||||
{
|
{
|
||||||
if (g_ncaKeysetLoaded) ret = (const u8*)(g_ncaKeyset.nca_header_key);
|
if (g_keysetLoaded) ret = (const u8*)(g_ncaKeyset.nca_header_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -309,9 +336,9 @@ const u8 *keysGetNcaMainSignatureModulus(u8 key_generation)
|
||||||
bool dev_unit = utilsIsDevelopmentUnit();
|
bool dev_unit = utilsIsDevelopmentUnit();
|
||||||
const u8 *ret = NULL, null_modulus[RSA2048_PUBKEY_SIZE] = {0};
|
const u8 *ret = NULL, null_modulus[RSA2048_PUBKEY_SIZE] = {0};
|
||||||
|
|
||||||
SCOPED_LOCK(&g_ncaKeysetMutex)
|
SCOPED_LOCK(&g_keysetMutex)
|
||||||
{
|
{
|
||||||
if (!g_ncaKeysetLoaded) break;
|
if (!g_keysetLoaded) break;
|
||||||
|
|
||||||
ret = (const u8*)(dev_unit ? g_ncaKeyset.nca_main_signature_moduli_dev[key_generation] : g_ncaKeyset.nca_main_signature_moduli_prod[key_generation]);
|
ret = (const u8*)(dev_unit ? g_ncaKeyset.nca_main_signature_moduli_dev[key_generation] : g_ncaKeyset.nca_main_signature_moduli_prod[key_generation]);
|
||||||
|
|
||||||
|
@ -348,9 +375,9 @@ bool keysDecryptNcaKeyAreaEntry(u8 kaek_index, u8 key_generation, void *dst, con
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
SCOPED_LOCK(&g_ncaKeysetMutex)
|
SCOPED_LOCK(&g_keysetMutex)
|
||||||
{
|
{
|
||||||
if (!g_ncaKeysetLoaded) break;
|
if (!g_keysetLoaded) break;
|
||||||
Result rc = splCryptoGenerateAesKey(g_ncaKeyset.nca_kaek_sealed[kaek_index][key_gen_val], src, dst);
|
Result rc = splCryptoGenerateAesKey(g_ncaKeyset.nca_kaek_sealed[kaek_index][key_gen_val], src, dst);
|
||||||
if (!(ret = R_SUCCEEDED(rc))) LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X).", rc);
|
if (!(ret = R_SUCCEEDED(rc))) LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X).", rc);
|
||||||
}
|
}
|
||||||
|
@ -376,9 +403,9 @@ const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
SCOPED_LOCK(&g_ncaKeysetMutex)
|
SCOPED_LOCK(&g_keysetMutex)
|
||||||
{
|
{
|
||||||
if (g_ncaKeysetLoaded) ret = (const u8*)(g_ncaKeyset.nca_kaek[kaek_index][key_gen_val]);
|
if (g_keysetLoaded) ret = (const u8*)(g_ncaKeyset.nca_kaek[kaek_index][key_gen_val]);
|
||||||
}
|
}
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
@ -395,9 +422,9 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o
|
||||||
|
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
SCOPED_LOCK(&g_ncaKeysetMutex)
|
SCOPED_LOCK(&g_keysetMutex)
|
||||||
{
|
{
|
||||||
if (!g_ncaKeysetLoaded) break;
|
if (!g_keysetLoaded) break;
|
||||||
|
|
||||||
size_t out_keydata_size = 0;
|
size_t out_keydata_size = 0;
|
||||||
u8 out_keydata[RSA2048_BYTES] = {0};
|
u8 out_keydata[RSA2048_BYTES] = {0};
|
||||||
|
@ -432,15 +459,27 @@ const u8 *keysGetTicketCommonKey(u8 key_generation)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
SCOPED_LOCK(&g_ncaKeysetMutex)
|
SCOPED_LOCK(&g_keysetMutex)
|
||||||
{
|
{
|
||||||
if (g_ncaKeysetLoaded) ret = (const u8*)(g_ncaKeyset.ticket_common_keys[key_gen_val]);
|
if (g_keysetLoaded) ret = (const u8*)(g_ncaKeyset.ticket_common_keys[key_gen_val]);
|
||||||
}
|
}
|
||||||
|
|
||||||
end:
|
end:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const u8 *keysGetGameCardInfoKey(void)
|
||||||
|
{
|
||||||
|
const u8 *ret = NULL;
|
||||||
|
|
||||||
|
SCOPED_LOCK(&g_keysetMutex)
|
||||||
|
{
|
||||||
|
if (g_keysetLoaded) ret = (const u8*)(utilsIsDevelopmentUnit() ? g_gameCardKeyset.gc_cardinfo_key_dev : g_gameCardKeyset.gc_cardinfo_key_prod);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static bool keysIsProductionModulus1xMandatory(void)
|
static bool keysIsProductionModulus1xMandatory(void)
|
||||||
{
|
{
|
||||||
return !utilsIsDevelopmentUnit();
|
return !utilsIsDevelopmentUnit();
|
||||||
|
@ -941,3 +980,34 @@ static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool keysDeriveGameCardKeys(void)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
/* Derive gc_cardinfo_kek_sealed from gc_cardinfo_kek_source. */
|
||||||
|
rc = splCryptoGenerateAesKek(g_gameCardKeyset.gc_cardinfo_kek_source, 0, 0, g_gameCardKeyset.gc_cardinfo_kek_sealed);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
LOG_MSG("splCryptoGenerateAesKek failed! (0x%08X) (gc_cardinfo_kek_sealed).", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Derive gc_cardinfo_key_prod from gc_cardinfo_kek_sealed and gc_cardinfo_key_prod_source. */
|
||||||
|
rc = splCryptoGenerateAesKey(g_gameCardKeyset.gc_cardinfo_kek_sealed, g_gameCardKeyset.gc_cardinfo_key_prod_source, g_gameCardKeyset.gc_cardinfo_key_prod);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X) (gc_cardinfo_key_prod).", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Derive gc_cardinfo_key_dev from gc_cardinfo_kek_sealed and gc_cardinfo_key_dev_source. */
|
||||||
|
rc = splCryptoGenerateAesKey(g_gameCardKeyset.gc_cardinfo_kek_sealed, g_gameCardKeyset.gc_cardinfo_key_dev_source, g_gameCardKeyset.gc_cardinfo_key_dev);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
LOG_MSG("splCryptoGenerateAesKey failed! (0x%08X) (gc_cardinfo_key_dev).", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -129,8 +129,8 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv)
|
||||||
/* Initialize USB Mass Storage interface. */
|
/* Initialize USB Mass Storage interface. */
|
||||||
if (!umsInitialize()) break;
|
if (!umsInitialize()) break;
|
||||||
|
|
||||||
/* Load NCA keyset. */
|
/* Load keyset. */
|
||||||
if (!keysLoadNcaKeyset()) break;
|
if (!keysLoadKeyset()) break;
|
||||||
|
|
||||||
/* Allocate NCA crypto buffer. */
|
/* Allocate NCA crypto buffer. */
|
||||||
if (!ncaAllocateCryptoBuffer())
|
if (!ncaAllocateCryptoBuffer())
|
||||||
|
|
2
todo.txt
2
todo.txt
|
@ -15,7 +15,7 @@ todo:
|
||||||
title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles
|
title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles
|
||||||
|
|
||||||
gamecard: functions to display filelist
|
gamecard: functions to display filelist
|
||||||
gamecard: check cardinfo's lafw version
|
gamecard: move around code to perform the lafw version check at the places we need
|
||||||
|
|
||||||
pfs0: functions to display filelist
|
pfs0: functions to display filelist
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue