diff --git a/source/gamecard.c b/source/gamecard.c index f6f7643..b67bfc1 100644 --- a/source/gamecard.c +++ b/source/gamecard.c @@ -48,7 +48,7 @@ typedef struct { u8 card_id_area[0x48]; u8 reserved[0x1B0]; FsGameCardCertificate certificate; - GameCardKeyArea key_area; + GameCardInitialData initial_data; } GameCardSecurityInformation; typedef struct { @@ -114,6 +114,7 @@ static MemoryLocation g_fsProgramMemory = { }; static GameCardSecurityInformation g_gameCardSecurityInfo = {0}; +static GameCardKeyArea g_gameCardKeyArea = {0}; /* Function prototypes. */ @@ -278,7 +279,7 @@ bool gamecardGetKeyArea(GameCardKeyArea *out) { mutexLock(&g_gamecardMutex); bool ret = (g_gameCardInserted && g_gameCardInfoLoaded && out); - if (ret) memcpy(out, &(g_gameCardSecurityInfo.key_area), sizeof(GameCardKeyArea)); + if (ret) memcpy(out, &g_gameCardKeyArea, sizeof(GameCardKeyArea)); mutexUnlock(&g_gamecardMutex); return ret; } @@ -711,10 +712,10 @@ static void gamecardLoadInfo(void) } } - /* Read full FS program memory to retrieve the GameCardSecurityInformation data, which holds the gamecard key area. */ + /* Read full FS program memory to retrieve the GameCardSecurityInformation data, which holds the gamecard initial data area. */ /* This must be performed while the gamecard is in secure mode, which is already taken care of in the gamecardReadStorageArea() calls from the last iteration in the previous for() loop. */ /* GameCardSecurityInformation data is returned by Lotus command "ChangeToSecureMode" (0xF), and kept in FS program memory only after the gamecard secure area has been both mounted and read from. */ - /* Under some circumstances, the gamecard key area is located *after* the GameCardSecurityInformation area (offset 0x600), instead of its common location at offset 0x400. */ + /* Under some circumstances, the gamecard initial data is located *after* the GameCardSecurityInformation area (offset 0x600), instead of its common location at offset 0x400. */ if (!gamecardReadSecurityInformation()) { LOGFILE("Failed to read gamecard security information area from FS program memory!"); @@ -732,6 +733,7 @@ static void gamecardFreeInfo(void) memset(&g_gameCardHeader, 0, sizeof(GameCardHeader)); memset(&g_gameCardSecurityInfo, 0, sizeof(GameCardSecurityInformation)); + memset(&g_gameCardKeyArea, 0, sizeof(GameCardKeyArea)); g_gameCardStorageNormalAreaSize = 0; g_gameCardStorageSecureAreaSize = 0; @@ -786,21 +788,26 @@ static bool gamecardReadSecurityInformation(void) memcpy(&g_gameCardSecurityInfo, g_fsProgramMemory.data + offset, sizeof(GameCardSecurityInformation)); /* Check the key_source / package_id value. */ - if (g_gameCardSecurityInfo.key_area.package_id == g_gameCardHeader.package_id) + if (g_gameCardSecurityInfo.initial_data.package_id == g_gameCardHeader.package_id) { /* Jackpot. */ found = true; } else { - /* Copy the sector right after the GameCardSecurityInformation element from the memory dump, since it may hold the gamecard key area. */ + /* Copy the sector right after the GameCardSecurityInformation element from the memory dump, since it may hold the gamecard initial data. */ offset += sizeof(GameCardSecurityInformation); - memcpy(&(g_gameCardSecurityInfo.key_area), g_fsProgramMemory.data + offset, sizeof(GameCardKeyArea)); - found = (g_gameCardSecurityInfo.key_area.package_id == g_gameCardHeader.package_id); + memcpy(&(g_gameCardSecurityInfo.initial_data), g_fsProgramMemory.data + offset, sizeof(GameCardInitialData)); + found = (g_gameCardSecurityInfo.initial_data.package_id == g_gameCardHeader.package_id); } break; } - if (!found) LOGFILE("Failed to locate gamecard key area!"); + if (found) + { + memcpy(&(g_gameCardKeyArea.initial_data), &(g_gameCardSecurityInfo.initial_data), sizeof(GameCardInitialData)); + } else { + LOGFILE("Failed to locate gamecard initial data area!"); + } /* Free FS memory dump. */ memFreeMemoryLocation(&g_fsProgramMemory); diff --git a/source/gamecard.h b/source/gamecard.h index 0156f83..68ddea5 100644 --- a/source/gamecard.h +++ b/source/gamecard.h @@ -36,18 +36,40 @@ ((x) == GameCardHashFileSystemPartitionType_Logo ? "logo" : ((x) == GameCardHashFileSystemPartitionType_Normal ? "normal" : \ ((x) == GameCardHashFileSystemPartitionType_Secure ? "secure" : "unknown"))))) +/// Plaintext area. Dumped from FS program memory. typedef struct { union { - u8 key_source[0x10]; + u8 key_source[0x10]; ///< Encrypted using AES-128-ECB with the common titlekek generator key (stored in the .rodata segment from the Lotus firmware). struct { - u64 package_id; ///< Matches package_id from GameCardHeader. - u64 padding; ///< Just zeroes. + u64 package_id; ///< Matches package_id from GameCardHeader. + u64 padding; ///< Just zeroes. }; }; - u8 encrypted_titlekey[0x10]; - u8 mac[0x10]; - u8 nonce[0xC]; + u8 encrypted_titlekey[0x10]; ///< Encrypted using AES-128-CCM with the decrypted key_source and the nonce from this section. + u8 mac[0x10]; ///< Used to verify the validity of the decrypted titlekey. + u8 nonce[0xC]; ///< Used as the IV to decrypt the key_source using AES-128-CCM. u8 reserved[0x1C4]; +} GameCardInitialData; + +/// Encrypted using AES-128-CTR with the key and IV/counter from the `GameCardTitleKeyEncryption` section. Assumed to be all zeroes in retail gamecards. +typedef struct { + u8 titlekey[0x10]; ///< Decrypted titlekey from the `GameCardInitialData` section. + u8 reserved[0xCF0]; +} GameCardTitleKey; + +/// Encrypted using RSA-2048-OAEP. Assumed to be all zeroes in retail gamecards. +typedef struct { + u8 titlekey_encryption_key[0x10]; ///< Used as the AES-128-CTR key for the `GameCardTitleKey` section. + u8 titlekey_encryption_iv[0x10]; ///< Used as the AES-128-CTR IV/counter for the `GameCardTitleKey` section. + u8 reserved[0xE0]; +} GameCardTitleKeyEncryption; + +/// Used to secure communications between the Lotus and the inserted gamecard. +/// Precedes the gamecard header. +typedef struct { + GameCardInitialData initial_data; + GameCardTitleKey titlekey_block; + GameCardTitleKeyEncryption titlekey_encryption; } GameCardKeyArea; typedef enum { @@ -99,21 +121,38 @@ typedef enum { } GameCardCompatibilityType; typedef struct { - u64 fw_version; ///< GameCardFwVersion. - u32 acc_ctrl; ///< GameCardAccCtrl. - u32 wait_1_time_read; ///< Always 0x1388. - u32 wait_2_time_read; ///< Always 0. - u32 wait_1_time_write; ///< Always 0. - u32 wait_2_time_write; ///< Always 0. - u32 fw_mode; - u32 upp_version; - u8 compatibility_type; ///< GameCardCompatibilityType. + u32 GameCardFwMode_Relstep : 8; + u32 GameCardFwMode_Micro : 8; + u32 GameCardFwMode_Minor : 8; + u32 GameCardFwMode_Major : 8; +} GameCardFwMode; + +typedef struct { + u32 GameCardUppVersion_MinorRelstep : 8; + u32 GameCardUppVersion_MajorRelstep : 8; + u32 GameCardUppVersion_Micro : 4; + u32 GameCardUppVersion_Minor : 6; + u32 GameCardUppVersion_Major : 6; +} GameCardUppVersion; + +/// Encrypted using AES-128-CBC with the `xci_header_key` (which can't dumped through current methods) and the IV from `GameCardHeader`. +typedef struct { + u64 fw_version; ///< GameCardFwVersion. + u32 acc_ctrl; ///< GameCardAccCtrl. + u32 wait_1_time_read; ///< Always 0x1388. + u32 wait_2_time_read; ///< Always 0. + u32 wait_1_time_write; ///< Always 0. + u32 wait_2_time_write; ///< Always 0. + GameCardFwMode fw_mode; + GameCardUppVersion upp_version; + u8 compatibility_type; ///< GameCardCompatibilityType. u8 reserved_1[0x3]; u64 upp_hash; - u64 upp_id; ///< Must match GAMECARD_UPDATE_TID. + u64 upp_id; ///< Must match GAMECARD_UPDATE_TID. u8 reserved_2[0x38]; -} GameCardExtendedHeader; +} GameCardHeaderEncryptedArea; +/// Placed after the `GameCardKeyArea` section. typedef struct { u8 signature[0x100]; ///< RSA-2048 PKCS #1 signature over the rest of the header. u32 magic; ///< "HEAD". @@ -135,7 +174,7 @@ typedef struct { u32 sel_t1_key_index; u32 sel_key_index; u32 normal_area_end_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks. - GameCardExtendedHeader extended_header; ///< Encrypted using AES-128-CBC with 'xci_header_key', which can't dumped through current methods. + GameCardHeaderEncryptedArea encrypted_area; } GameCardHeader; typedef enum { diff --git a/source/nca.h b/source/nca.h index bc3445e..92b62f8 100644 --- a/source/nca.h +++ b/source/nca.h @@ -75,10 +75,10 @@ typedef enum { } NcaKeyAreaEncryptionKeyIndex; typedef struct { - u8 relstep; - u8 micro; - u8 minor; - u8 major; + u32 NcaSdkAddOnVersion_Relstep : 8; + u32 NcaSdkAddOnVersion_Micro : 8; + u32 NcaSdkAddOnVersion_Minor : 8; + u32 NcaSdkAddOnVersion_Major : 8; } NcaSdkAddOnVersion; /// 'NcaKeyGeneration_Current' will always point to the last known key generation value. diff --git a/source/romfs.h b/source/romfs.h index c67150e..728ffe9 100644 --- a/source/romfs.h +++ b/source/romfs.h @@ -101,6 +101,7 @@ typedef struct { u64 file_table_size; ///< RomFS file entries table size. RomFileSystemFileEntry *file_table; ///< RomFS file entries table. u64 body_offset; ///< RomFS file data body offset (relative to the start of the RomFS). + u32 cur_dir_offset; ///< Current RomFS directory offset (relative to the start of the directory entries table). Used for RomFS browsing. } RomFileSystemContext; typedef struct { diff --git a/source/rsa.h b/source/rsa.h index 34d516d..62581b8 100644 --- a/source/rsa.h +++ b/source/rsa.h @@ -34,7 +34,7 @@ bool rsa2048GenerateSha256BasedCustomAcidSignature(void *dst, const void *src, s /// Suitable to replace the ACID public key in main.npdm files. const u8 *rsa2048GetCustomAcidPublicKey(void); -/// Performs RSA-2048 OAEP decryption and verification. Used to decrypt the titlekey block from tickets with personalized crypto. +/// Performs RSA-2048-OAEP decryption and verification. Used to decrypt the titlekey block from tickets with personalized crypto. bool rsa2048OaepDecryptAndVerify(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *exponent, size_t exponent_size, const void *label_hash, size_t *out_size); #endif /* __RSA_H__ */ diff --git a/source/tik.c b/source/tik.c index 8cfbaed..40fb415 100644 --- a/source/tik.c +++ b/source/tik.c @@ -21,6 +21,7 @@ #include "utils.h" #include "tik.h" +#include "cert.h" #include "save.h" #include "es.h" #include "keys.h" diff --git a/todo.txt b/todo.txt index 6cb86c6..fa79aac 100644 --- a/todo.txt +++ b/todo.txt @@ -2,6 +2,7 @@ todo: hfs0: filelist generation methods + tik: make sure the common crypto cert is available when fakesigning a ticket tik: automatically dump tickets to the SD card? tik: use dumped tickets when the original ones can't be found in the ES savefile?