2020-04-16 01:06:41 +01:00
/*
2020-07-03 10:31:22 +01:00
* keys . c
2020-04-16 01:06:41 +01:00
*
2020-07-03 10:31:22 +01:00
* Copyright ( c ) 2018 - 2020 , SciresM .
* Copyright ( c ) 2019 , shchmue .
2022-03-17 12:58:40 +00:00
* Copyright ( c ) 2020 - 2022 , DarkMatterCore < pabloacurielz @ gmail . com > .
2020-07-03 10:31:22 +01:00
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
2021-03-25 19:26:58 +00:00
* nxdumptool is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
2020-04-16 01:06:41 +01:00
*
2021-03-25 19:26:58 +00:00
* nxdumptool is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2020-04-16 01:06:41 +01:00
*
* You should have received a copy of the GNU General Public License
2021-03-25 19:26:58 +00:00
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
2020-04-16 01:06:41 +01:00
*/
2021-03-26 04:35:14 +00:00
# include "nxdt_utils.h"
2020-04-11 06:28:26 +01:00
# include "keys.h"
2020-07-13 07:36:17 +01:00
# include "mem.h"
2020-04-11 06:28:26 +01:00
# include "nca.h"
2021-05-11 07:00:33 +01:00
# include "rsa.h"
2020-04-11 06:28:26 +01:00
2021-05-11 07:00:33 +01:00
# define ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT 0x10001
2020-04-22 21:53:20 +01:00
2020-04-16 11:13:11 +01:00
/* Type definitions. */
2021-05-18 13:32:43 +01:00
typedef bool ( * KeysIsKeyMandatoryFunction ) ( void ) ; /* Used to determine if a key is mandatory or not at runtime. */
2020-04-11 06:28:26 +01:00
typedef struct {
char name [ 64 ] ;
u8 hash [ SHA256_HASH_SIZE ] ;
u64 size ;
void * dst ;
2021-05-18 13:32:43 +01:00
KeysIsKeyMandatoryFunction mandatory_func ; ///< If NULL, key is mandatory.
2020-07-13 07:36:17 +01:00
} KeysMemoryKey ;
2020-04-11 06:28:26 +01:00
typedef struct {
2020-07-13 07:36:17 +01:00
MemoryLocation location ;
2020-04-11 06:28:26 +01:00
u32 key_count ;
2020-07-13 07:36:17 +01:00
KeysMemoryKey keys [ ] ;
} KeysMemoryInfo ;
2020-04-11 06:28:26 +01:00
typedef struct {
2021-05-11 07:00:33 +01:00
///< AES-128-XTS key needed to handle NCA header crypto.
u8 nca_header_kek_source [ AES_128_KEY_SIZE ] ; ///< Retrieved from the .rodata segment in the FS sysmodule.
u8 nca_header_key_source [ AES_128_KEY_SIZE * 2 ] ; ///< Retrieved from the .data segment in the FS sysmodule.
u8 nca_header_kek_sealed [ AES_128_KEY_SIZE ] ; ///< Generated from nca_header_kek_source. Sealed by the SMC AES engine.
u8 nca_header_key [ AES_128_KEY_SIZE * 2 ] ; ///< Generated from nca_header_kek_sealed and nca_header_key_source.
2021-05-18 13:32:43 +01:00
///< RSA-2048-PSS moduli used to verify the main signature from NCA headers.
u8 nca_main_signature_moduli_prod [ NcaMainSignatureKeyGeneration_Max ] [ RSA2048_PUBKEY_SIZE ] ; ///< Moduli used in retail units. Retrieved from the .rodata segment in the FS sysmodule.
u8 nca_main_signature_moduli_dev [ NcaMainSignatureKeyGeneration_Max ] [ RSA2048_PUBKEY_SIZE ] ; ///< Moduli used in development units. Retrieved from the .rodata segment in the FS sysmodule.
2021-05-11 07:00:33 +01:00
///< AES-128-ECB keys needed to handle key area crypto from NCA headers.
u8 nca_kaek_sources [ NcaKeyAreaEncryptionKeyIndex_Count ] [ AES_128_KEY_SIZE ] ; ///< Retrieved from the .rodata segment in the FS sysmodule.
u8 nca_kaek_sealed [ NcaKeyAreaEncryptionKeyIndex_Count ] [ NcaKeyGeneration_Max ] [ AES_128_KEY_SIZE ] ; ///< Generated from nca_kaek_sources. Sealed by the SMC AES engine.
u8 nca_kaek [ NcaKeyAreaEncryptionKeyIndex_Count ] [ NcaKeyGeneration_Max ] [ AES_128_KEY_SIZE ] ; ///< Unsealed key area encryption keys. Retrieved from the Lockpick_RCM keys file.
///< AES-128-CTR key needed to decrypt the console-specific eTicket RSA device key stored in PRODINFO.
u8 eticket_rsa_kek [ AES_128_KEY_SIZE ] ; ///< eTicket RSA key encryption key (generic). Retrieved from the Lockpick_RCM keys file.
u8 eticket_rsa_kek_personalized [ AES_128_KEY_SIZE ] ; ///< eTicket RSA key encryption key (console-specific). Retrieved from the Lockpick_RCM keys file.
///< AES-128-ECB keys needed to decrypt titlekeys.
u8 ticket_common_keys [ NcaKeyGeneration_Max ] [ AES_128_KEY_SIZE ] ; ///< Retrieved from the Lockpick_RCM keys file.
} KeysNcaKeyset ;
2021-05-22 09:45:40 +01:00
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 ;
2021-05-11 07:00:33 +01:00
/// 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.
typedef struct {
2021-05-21 14:34:43 +01:00
u8 ctr [ AES_128_KEY_SIZE ] ;
u8 private_exponent [ RSA2048_BYTES ] ;
u8 modulus [ RSA2048_BYTES ] ;
2021-05-22 09:45:40 +01:00
u32 public_exponent ; ///< Stored using big endian byte order. Must match ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT.
2021-05-11 07:00:33 +01:00
u8 padding [ 0x14 ] ;
u64 device_id ;
u8 ghash [ 0x10 ] ;
} EticketRsaDeviceKey ;
NXDT_ASSERT ( EticketRsaDeviceKey , 0x240 ) ;
2020-04-11 06:28:26 +01:00
2021-05-18 13:32:43 +01:00
/* Function prototypes. */
static bool keysIsProductionModulus1xMandatory ( void ) ;
static bool keysIsProductionModulus9xMandatory ( void ) ;
static bool keysIsDevelopmentModulus1xMandatory ( void ) ;
static bool keysIsDevelopmentModulus9xMandatory ( void ) ;
static bool keysRetrieveKeysFromProgramMemory ( KeysMemoryInfo * info ) ;
static bool keysDeriveNcaHeaderKey ( void ) ;
static bool keysDeriveSealedNcaKeyAreaEncryptionKeys ( void ) ;
2022-04-18 22:38:18 +01:00
static int keysGetKeyAndValueFromFile ( FILE * f , char * * line , char * * key , char * * value ) ;
static char keysConvertHexDigitToBinary ( char c ) ;
2021-05-18 13:32:43 +01:00
static bool keysParseHexKey ( u8 * out , const char * key , const char * value , u32 size ) ;
static bool keysReadKeysFromFile ( void ) ;
static bool keysGetDecryptedEticketRsaDeviceKey ( void ) ;
static bool keysTestEticketRsaDeviceKey ( const void * e , const void * d , const void * n ) ;
2021-05-22 09:45:40 +01:00
static bool keysDeriveGameCardKeys ( void ) ;
2020-04-16 11:13:11 +01:00
/* Global variables. */
2021-05-11 07:00:33 +01:00
static KeysNcaKeyset g_ncaKeyset = { 0 } ;
2021-05-22 09:45:40 +01:00
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 ;
2020-04-11 06:28:26 +01:00
2021-05-11 07:00:33 +01:00
static SetCalRsa2048DeviceKey g_eTicketRsaDeviceKey = { 0 } ;
2020-07-13 07:36:17 +01:00
static KeysMemoryInfo g_fsRodataMemoryInfo = {
2020-04-11 06:28:26 +01:00
. location = {
. program_id = FS_SYSMODULE_TID ,
2020-07-13 07:36:17 +01:00
. mask = MemoryProgramSegmentType_Rodata ,
2020-04-11 06:28:26 +01:00
. data = NULL ,
. data_size = 0
} ,
2021-05-18 13:32:43 +01:00
. key_count = 8 ,
2020-04-11 06:28:26 +01:00
. keys = {
{
2021-05-11 07:00:33 +01:00
. name = " nca_header_kek_source " ,
. hash = {
0x18 , 0x88 , 0xCA , 0xED , 0x55 , 0x51 , 0xB3 , 0xED , 0xE0 , 0x14 , 0x99 , 0xE8 , 0x7C , 0xE0 , 0xD8 , 0x68 ,
0x27 , 0xF8 , 0x08 , 0x20 , 0xEF , 0xB2 , 0x75 , 0x92 , 0x10 , 0x55 , 0xAA , 0x4E , 0x2A , 0xBD , 0xFF , 0xC2
} ,
2021-05-18 13:32:43 +01:00
. size = sizeof ( g_ncaKeyset . nca_header_kek_source ) ,
. dst = g_ncaKeyset . nca_header_kek_source ,
. mandatory_func = NULL
} ,
{
. name = " nca_main_signature_modulus_prod_00 " ,
. hash = {
0xF9 , 0x2E , 0x84 , 0x98 , 0x17 , 0x2C , 0xAF , 0x9C , 0x20 , 0xE3 , 0xF1 , 0xF7 , 0xD3 , 0xE7 , 0x2C , 0x62 ,
0x50 , 0xA9 , 0x40 , 0x7A , 0xE7 , 0x84 , 0xE0 , 0x03 , 0x58 , 0x07 , 0x85 , 0xA5 , 0x68 , 0x0B , 0x80 , 0x33
} ,
2021-06-03 15:46:21 +01:00
. size = sizeof ( g_ncaKeyset . nca_main_signature_moduli_prod [ NcaMainSignatureKeyGeneration_Since100NUP ] ) ,
. dst = g_ncaKeyset . nca_main_signature_moduli_prod [ NcaMainSignatureKeyGeneration_Since100NUP ] ,
2021-05-18 13:32:43 +01:00
. mandatory_func = & keysIsProductionModulus1xMandatory
} ,
{
. name = " nca_main_signature_modulus_prod_01 " ,
. hash = {
0x5F , 0x6B , 0xE3 , 0x1C , 0x31 , 0x6E , 0x7C , 0xB2 , 0x1C , 0xA7 , 0xB9 , 0xA1 , 0x70 , 0x6A , 0x9D , 0x58 ,
0x04 , 0xEB , 0x90 , 0x53 , 0x72 , 0xEF , 0xCB , 0x56 , 0xD1 , 0x93 , 0xF2 , 0xAF , 0x9E , 0x8A , 0xD1 , 0xFA
} ,
2021-06-03 15:46:21 +01:00
. size = sizeof ( g_ncaKeyset . nca_main_signature_moduli_prod [ NcaMainSignatureKeyGeneration_Since900NUP ] ) ,
. dst = g_ncaKeyset . nca_main_signature_moduli_prod [ NcaMainSignatureKeyGeneration_Since900NUP ] ,
2021-05-18 13:32:43 +01:00
. mandatory_func = & keysIsProductionModulus9xMandatory
} ,
{
. name = " nca_main_signature_modulus_dev_00 " ,
. hash = {
0x50 , 0xF8 , 0x26 , 0xBB , 0x13 , 0xFE , 0xB2 , 0x6D , 0x83 , 0xCF , 0xFF , 0xD8 , 0x38 , 0x45 , 0xC3 , 0x51 ,
0x4D , 0xCB , 0x06 , 0x91 , 0x83 , 0x52 , 0x06 , 0x35 , 0x7A , 0xC1 , 0xDA , 0x6B , 0xF1 , 0x60 , 0x9F , 0x18
} ,
2021-06-03 15:46:21 +01:00
. size = sizeof ( g_ncaKeyset . nca_main_signature_moduli_dev [ NcaMainSignatureKeyGeneration_Since100NUP ] ) ,
. dst = g_ncaKeyset . nca_main_signature_moduli_dev [ NcaMainSignatureKeyGeneration_Since100NUP ] ,
2021-05-18 13:32:43 +01:00
. mandatory_func = & keysIsDevelopmentModulus1xMandatory
} ,
{
. name = " nca_main_signature_modulus_dev_01 " ,
. hash = {
0x56 , 0xF5 , 0x06 , 0xEF , 0x8E , 0xCA , 0x2A , 0x29 , 0x6F , 0x65 , 0x45 , 0xE1 , 0x87 , 0x60 , 0x01 , 0x11 ,
0xBC , 0xC7 , 0x38 , 0x56 , 0x99 , 0x16 , 0xAD , 0xA5 , 0xDD , 0x89 , 0xF2 , 0xE9 , 0xAB , 0x28 , 0x5B , 0x18
} ,
2021-06-03 15:46:21 +01:00
. size = sizeof ( g_ncaKeyset . nca_main_signature_moduli_dev [ NcaMainSignatureKeyGeneration_Since900NUP ] ) ,
. dst = g_ncaKeyset . nca_main_signature_moduli_dev [ NcaMainSignatureKeyGeneration_Since900NUP ] ,
2021-05-18 13:32:43 +01:00
. mandatory_func = & keysIsDevelopmentModulus9xMandatory
2020-04-11 06:28:26 +01:00
} ,
{
2021-05-11 07:00:33 +01:00
. name = " nca_kaek_application_source " ,
. hash = {
0x04 , 0xAD , 0x66 , 0x14 , 0x3C , 0x72 , 0x6B , 0x2A , 0x13 , 0x9F , 0xB6 , 0xB2 , 0x11 , 0x28 , 0xB4 , 0x6F ,
0x56 , 0xC5 , 0x53 , 0xB2 , 0xB3 , 0x88 , 0x71 , 0x10 , 0x30 , 0x42 , 0x98 , 0xD8 , 0xD0 , 0x09 , 0x2D , 0x9E
} ,
2021-05-18 13:32:43 +01:00
. size = sizeof ( g_ncaKeyset . nca_kaek_sources [ NcaKeyAreaEncryptionKeyIndex_Application ] ) ,
. dst = g_ncaKeyset . nca_kaek_sources [ NcaKeyAreaEncryptionKeyIndex_Application ] ,
. mandatory_func = NULL
2020-04-11 06:28:26 +01:00
} ,
{
2021-05-11 07:00:33 +01:00
. name = " nca_kaek_ocean_source " ,
. hash = {
0xFD , 0x43 , 0x40 , 0x00 , 0xC8 , 0xFF , 0x2B , 0x26 , 0xF8 , 0xE9 , 0xA9 , 0xD2 , 0xD2 , 0xC1 , 0x2F , 0x6B ,
0xE5 , 0x77 , 0x3C , 0xBB , 0x9D , 0xC8 , 0x63 , 0x00 , 0xE1 , 0xBD , 0x99 , 0xF8 , 0xEA , 0x33 , 0xA4 , 0x17
} ,
2021-05-18 13:32:43 +01:00
. size = sizeof ( g_ncaKeyset . nca_kaek_sources [ NcaKeyAreaEncryptionKeyIndex_Ocean ] ) ,
. dst = g_ncaKeyset . nca_kaek_sources [ NcaKeyAreaEncryptionKeyIndex_Ocean ] ,
. mandatory_func = NULL
2020-04-11 06:28:26 +01:00
} ,
{
2021-05-11 07:00:33 +01:00
. name = " nca_kaek_system_source " ,
. hash = {
0x1F , 0x17 , 0xB1 , 0xFD , 0x51 , 0xAD , 0x1C , 0x23 , 0x79 , 0xB5 , 0x8F , 0x15 , 0x2C , 0xA4 , 0x91 , 0x2E ,
0xC2 , 0x10 , 0x64 , 0x41 , 0xE5 , 0x17 , 0x22 , 0xF3 , 0x87 , 0x00 , 0xD5 , 0x93 , 0x7A , 0x11 , 0x62 , 0xF7
} ,
2021-05-18 13:32:43 +01:00
. size = sizeof ( g_ncaKeyset . nca_kaek_sources [ NcaKeyAreaEncryptionKeyIndex_System ] ) ,
. dst = g_ncaKeyset . nca_kaek_sources [ NcaKeyAreaEncryptionKeyIndex_System ] ,
. mandatory_func = NULL
2020-04-11 06:28:26 +01:00
}
}
} ;
2020-07-13 07:36:17 +01:00
static KeysMemoryInfo g_fsDataMemoryInfo = {
2020-04-11 06:28:26 +01:00
. location = {
. program_id = FS_SYSMODULE_TID ,
2020-07-13 07:36:17 +01:00
. mask = MemoryProgramSegmentType_Data ,
2020-04-11 06:28:26 +01:00
. data = NULL ,
. data_size = 0
} ,
. key_count = 1 ,
. keys = {
{
2021-05-11 07:00:33 +01:00
. name = " nca_header_key_source " ,
. hash = {
0x8F , 0x78 , 0x3E , 0x46 , 0x85 , 0x2D , 0xF6 , 0xBE , 0x0B , 0xA4 , 0xE1 , 0x92 , 0x73 , 0xC4 , 0xAD , 0xBA ,
0xEE , 0x16 , 0x38 , 0x00 , 0x43 , 0xE1 , 0xB8 , 0xC4 , 0x18 , 0xC4 , 0x08 , 0x9A , 0x8B , 0xD6 , 0x4A , 0xA6
} ,
2021-05-18 13:32:43 +01:00
. size = sizeof ( g_ncaKeyset . nca_header_key_source ) ,
. dst = g_ncaKeyset . nca_header_key_source ,
. mandatory_func = NULL
2020-04-11 06:28:26 +01:00
}
}
} ;
2021-05-22 09:45:40 +01:00
bool keysLoadKeyset ( void )
2020-04-11 06:28:26 +01:00
{
2021-05-18 13:32:43 +01:00
bool ret = false ;
2020-04-11 06:28:26 +01:00
2021-05-22 09:45:40 +01:00
SCOPED_LOCK ( & g_keysetMutex )
2021-05-11 07:00:33 +01:00
{
2021-05-22 09:45:40 +01:00
ret = g_keysetLoaded ;
2021-05-18 13:32:43 +01:00
if ( ret ) break ;
/* Retrieve FS .rodata keys. */
if ( ! keysRetrieveKeysFromProgramMemory ( & g_fsRodataMemoryInfo ) )
{
LOG_MSG ( " Unable to retrieve keys from FS .rodata segment! " ) ;
break ;
}
/* Retrieve FS .data keys. */
if ( ! keysRetrieveKeysFromProgramMemory ( & g_fsDataMemoryInfo ) )
{
LOG_MSG ( " Unable to retrieve keys from FS .data segment! " ) ;
break ;
}
/* Derive NCA header key. */
if ( ! keysDeriveNcaHeaderKey ( ) )
{
LOG_MSG ( " Unable to derive NCA header key! " ) ;
break ;
}
/* Derive sealed NCA KAEKs. */
if ( ! keysDeriveSealedNcaKeyAreaEncryptionKeys ( ) )
{
LOG_MSG ( " Unable to derive sealed NCA KAEKs! " ) ;
break ;
}
/* Read additional keys from the keys file. */
if ( ! keysReadKeysFromFile ( ) ) break ;
/* Get decrypted eTicket RSA device key. */
if ( ! keysGetDecryptedEticketRsaDeviceKey ( ) ) break ;
2021-05-22 09:45:40 +01:00
/* Derive gamecard keys. */
if ( ! keysDeriveGameCardKeys ( ) ) break ;
2021-05-18 13:32:43 +01:00
/* Update flags. */
2021-05-22 09:45:40 +01:00
ret = g_keysetLoaded = true ;
2021-05-11 07:00:33 +01:00
}
/*if (ret)
{
LOG_DATA ( & g_ncaKeyset , sizeof ( KeysNcaKeyset ) , " NCA keyset dump: " ) ;
LOG_DATA ( & g_eTicketRsaDeviceKey , sizeof ( SetCalRsa2048DeviceKey ) , " eTicket RSA device key dump: " ) ;
2021-05-22 09:45:40 +01:00
LOG_DATA ( & g_gameCardKeyset , sizeof ( KeysGameCardKeyset ) , " Gamecard keyset dump: " ) ;
2021-05-11 07:00:33 +01:00
} */
2020-05-03 00:40:50 +01:00
return ret ;
2020-04-11 06:28:26 +01:00
}
const u8 * keysGetNcaHeaderKey ( void )
{
2021-05-18 13:32:43 +01:00
const u8 * ret = NULL ;
2021-05-22 09:45:40 +01:00
SCOPED_LOCK ( & g_keysetMutex )
2021-05-18 13:32:43 +01:00
{
2021-05-22 09:45:40 +01:00
if ( g_keysetLoaded ) ret = ( const u8 * ) ( g_ncaKeyset . nca_header_key ) ;
2021-05-18 13:32:43 +01:00
}
return ret ;
2020-04-11 06:28:26 +01:00
}
2021-05-21 14:34:43 +01:00
const u8 * keysGetNcaMainSignatureModulus ( u8 key_generation )
{
if ( key_generation > NcaMainSignatureKeyGeneration_Current )
{
2022-01-20 17:09:03 +00:00
LOG_MSG ( " Unsupported key generation value! (0x%02X). " , key_generation ) ;
2021-05-21 14:34:43 +01:00
return NULL ;
}
bool dev_unit = utilsIsDevelopmentUnit ( ) ;
const u8 * ret = NULL , null_modulus [ RSA2048_PUBKEY_SIZE ] = { 0 } ;
2021-05-22 09:45:40 +01:00
SCOPED_LOCK ( & g_keysetMutex )
2021-05-21 14:34:43 +01:00
{
2021-05-22 09:45:40 +01:00
if ( ! g_keysetLoaded ) break ;
2021-05-21 14:34:43 +01:00
ret = ( const u8 * ) ( dev_unit ? g_ncaKeyset . nca_main_signature_moduli_dev [ key_generation ] : g_ncaKeyset . nca_main_signature_moduli_prod [ key_generation ] ) ;
if ( ! memcmp ( ret , null_modulus , RSA2048_PUBKEY_SIZE ) )
{
LOG_MSG ( " %s NCA header main signature modulus 0x%02X unavailable. " , dev_unit ? " Development " : " Retail " , key_generation ) ;
ret = NULL ;
}
}
return ret ;
}
2021-05-11 07:00:33 +01:00
bool keysDecryptNcaKeyAreaEntry ( u8 kaek_index , u8 key_generation , void * dst , const void * src )
2020-04-11 06:28:26 +01:00
{
2021-05-18 13:32:43 +01:00
bool ret = false ;
2021-05-11 07:00:33 +01:00
u8 key_gen_val = ( key_generation ? ( key_generation - 1 ) : key_generation ) ;
2020-04-11 06:28:26 +01:00
2021-05-11 07:00:33 +01:00
if ( kaek_index > = NcaKeyAreaEncryptionKeyIndex_Count )
2020-04-11 06:28:26 +01:00
{
2021-05-11 07:00:33 +01:00
LOG_MSG ( " Invalid KAEK index! (0x%02X). " , kaek_index ) ;
goto end ;
2020-04-11 06:28:26 +01:00
}
2021-05-11 07:00:33 +01:00
if ( key_gen_val > = NcaKeyGeneration_Max )
{
LOG_MSG ( " Invalid key generation value! (0x%02X). " , key_gen_val ) ;
goto end ;
}
if ( ! dst | | ! src )
{
LOG_MSG ( " Invalid destination/source pointer. " ) ;
goto end ;
}
2021-05-22 09:45:40 +01:00
SCOPED_LOCK ( & g_keysetMutex )
2021-05-11 07:00:33 +01:00
{
2021-05-22 09:45:40 +01:00
if ( ! g_keysetLoaded ) break ;
2021-05-18 13:32:43 +01:00
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 ) ;
2021-05-11 07:00:33 +01:00
}
end :
2021-05-18 13:32:43 +01:00
return ret ;
2020-04-11 06:28:26 +01:00
}
2021-05-11 07:00:33 +01:00
const u8 * keysGetNcaKeyAreaEncryptionKey ( u8 kaek_index , u8 key_generation )
2020-04-11 06:28:26 +01:00
{
2021-05-18 13:32:43 +01:00
const u8 * ret = NULL ;
2021-05-11 07:00:33 +01:00
u8 key_gen_val = ( key_generation ? ( key_generation - 1 ) : key_generation ) ;
if ( kaek_index > = NcaKeyAreaEncryptionKeyIndex_Count )
{
LOG_MSG ( " Invalid KAEK index! (0x%02X). " , kaek_index ) ;
goto end ;
}
if ( key_gen_val > = NcaKeyGeneration_Max )
2020-04-11 06:28:26 +01:00
{
2021-05-11 07:00:33 +01:00
LOG_MSG ( " Invalid key generation value! (0x%02X). " , key_gen_val ) ;
goto end ;
2020-04-11 06:28:26 +01:00
}
2021-05-22 09:45:40 +01:00
SCOPED_LOCK ( & g_keysetMutex )
2021-05-18 13:32:43 +01:00
{
2021-05-22 09:45:40 +01:00
if ( g_keysetLoaded ) ret = ( const u8 * ) ( g_ncaKeyset . nca_kaek [ kaek_index ] [ key_gen_val ] ) ;
2021-05-18 13:32:43 +01:00
}
2021-05-11 07:00:33 +01:00
end :
2021-05-18 13:32:43 +01:00
return ret ;
2020-04-11 06:28:26 +01:00
}
2021-05-11 07:00:33 +01:00
bool keysDecryptRsaOaepWrappedTitleKey ( const void * rsa_wrapped_titlekey , void * out_titlekey )
2020-04-11 06:28:26 +01:00
{
2021-05-11 07:00:33 +01:00
if ( ! rsa_wrapped_titlekey | | ! out_titlekey )
2020-04-11 06:28:26 +01:00
{
2021-05-11 07:00:33 +01:00
LOG_MSG ( " Invalid parameters! " ) ;
return false ;
2020-04-11 06:28:26 +01:00
}
2021-05-18 13:32:43 +01:00
bool ret = false ;
2021-05-11 07:00:33 +01:00
2021-05-22 09:45:40 +01:00
SCOPED_LOCK ( & g_keysetMutex )
2020-04-11 06:28:26 +01:00
{
2021-05-22 09:45:40 +01:00
if ( ! g_keysetLoaded ) break ;
2021-05-18 13:32:43 +01:00
size_t out_keydata_size = 0 ;
2021-05-21 14:34:43 +01:00
u8 out_keydata [ RSA2048_BYTES ] = { 0 } ;
2021-05-18 13:32:43 +01:00
2021-05-11 07:00:33 +01:00
/* Get eTicket RSA device key. */
2021-05-18 13:32:43 +01:00
EticketRsaDeviceKey * eticket_rsa_key = ( EticketRsaDeviceKey * ) g_eTicketRsaDeviceKey . key ;
2021-05-11 07:00:33 +01:00
/* Perform a RSA-OAEP unwrap operation to get the encrypted titlekey. */
2021-05-21 14:34:43 +01:00
/* ES uses a NULL string as the label. */
ret = ( rsa2048OaepDecrypt ( out_keydata , sizeof ( out_keydata ) , rsa_wrapped_titlekey , eticket_rsa_key - > modulus , & ( eticket_rsa_key - > public_exponent ) , sizeof ( eticket_rsa_key - > public_exponent ) , \
eticket_rsa_key - > private_exponent , sizeof ( eticket_rsa_key - > private_exponent ) , NULL , 0 , & out_keydata_size ) & & out_keydata_size > = AES_128_KEY_SIZE ) ;
2021-05-18 13:32:43 +01:00
if ( ret )
2021-05-11 07:00:33 +01:00
{
/* Copy RSA-OAEP unwrapped titlekey. */
memcpy ( out_titlekey , out_keydata , AES_128_KEY_SIZE ) ;
} else {
LOG_MSG ( " RSA-OAEP titlekey decryption failed! " ) ;
}
2020-04-11 06:28:26 +01:00
}
2021-05-18 13:32:43 +01:00
return ret ;
2021-05-11 07:00:33 +01:00
}
const u8 * keysGetTicketCommonKey ( u8 key_generation )
{
2021-05-18 13:32:43 +01:00
const u8 * ret = NULL ;
2020-04-11 06:28:26 +01:00
u8 key_gen_val = ( key_generation ? ( key_generation - 1 ) : key_generation ) ;
2021-05-11 07:00:33 +01:00
if ( key_gen_val > = NcaKeyGeneration_Max )
{
LOG_MSG ( " Invalid key generation value! (0x%02X). " , key_gen_val ) ;
goto end ;
}
2021-05-22 09:45:40 +01:00
SCOPED_LOCK ( & g_keysetMutex )
2021-05-18 13:32:43 +01:00
{
2021-05-22 09:45:40 +01:00
if ( g_keysetLoaded ) ret = ( const u8 * ) ( g_ncaKeyset . ticket_common_keys [ key_gen_val ] ) ;
2021-05-18 13:32:43 +01:00
}
2021-05-11 07:00:33 +01:00
end :
2021-05-18 13:32:43 +01:00
return ret ;
}
2021-05-22 09:45:40 +01:00
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 ;
}
2021-05-18 13:32:43 +01:00
static bool keysIsProductionModulus1xMandatory ( void )
{
return ! utilsIsDevelopmentUnit ( ) ;
}
static bool keysIsProductionModulus9xMandatory ( void )
{
return ( ! utilsIsDevelopmentUnit ( ) & & hosversionAtLeast ( 9 , 0 , 0 ) ) ;
}
static bool keysIsDevelopmentModulus1xMandatory ( void )
{
return utilsIsDevelopmentUnit ( ) ;
}
static bool keysIsDevelopmentModulus9xMandatory ( void )
{
return ( utilsIsDevelopmentUnit ( ) & & hosversionAtLeast ( 9 , 0 , 0 ) ) ;
2020-04-11 06:28:26 +01:00
}
2020-07-13 07:36:17 +01:00
static bool keysRetrieveKeysFromProgramMemory ( KeysMemoryInfo * info )
2020-04-11 06:28:26 +01:00
{
if ( ! info | | ! info - > key_count )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
u8 tmp_hash [ SHA256_HASH_SIZE ] ;
bool success = false ;
2020-07-13 07:36:17 +01:00
if ( ! memRetrieveProgramMemorySegment ( & ( info - > location ) ) ) return false ;
2020-04-11 06:28:26 +01:00
for ( u32 i = 0 ; i < info - > key_count ; i + + )
{
2020-10-14 19:58:33 +01:00
KeysMemoryKey * key = & ( info - > keys [ i ] ) ;
2021-05-18 13:32:43 +01:00
bool found = false , mandatory = ( key - > mandatory_func ! = NULL ? key - > mandatory_func ( ) : true ) ;
2020-10-14 19:58:33 +01:00
2022-02-09 04:38:13 +00:00
/* Skip key if it's not mandatory. */
if ( ! mandatory ) continue ;
/* Check destination pointer. */
2020-10-14 19:58:33 +01:00
if ( ! key - > dst )
2020-04-11 06:28:26 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid destination pointer for key \" %s \" in program %016lX! " , key - > name , info - > location . program_id ) ;
2022-02-09 04:38:13 +00:00
goto end ;
2020-04-11 06:28:26 +01:00
}
2020-07-06 01:10:07 +01:00
/* Hash every key length-sized byte chunk in the process memory buffer until a match is found. */
2020-04-11 06:28:26 +01:00
for ( u64 j = 0 ; j < info - > location . data_size ; j + + )
{
2020-10-14 19:58:33 +01:00
if ( ( info - > location . data_size - j ) < key - > size ) break ;
2020-04-11 06:28:26 +01:00
2020-10-14 19:58:33 +01:00
sha256CalculateHash ( tmp_hash , info - > location . data + j , key - > size ) ;
2020-04-11 06:28:26 +01:00
2020-10-14 19:58:33 +01:00
if ( ! memcmp ( tmp_hash , key - > hash , SHA256_HASH_SIZE ) )
2020-04-11 06:28:26 +01:00
{
2020-07-06 01:10:07 +01:00
/* Jackpot. */
2020-10-14 19:58:33 +01:00
memcpy ( key - > dst , info - > location . data + j , key - > size ) ;
2020-04-11 06:28:26 +01:00
found = true ;
break ;
}
}
if ( ! found )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Unable to locate key \" %s \" in process memory from program %016lX! " , key - > name , info - > location . program_id ) ;
2022-02-09 04:38:13 +00:00
goto end ;
2020-04-11 06:28:26 +01:00
}
}
success = true ;
2020-07-13 07:36:17 +01:00
end :
memFreeMemoryLocation ( & ( info - > location ) ) ;
2020-04-11 06:28:26 +01:00
return success ;
}
static bool keysDeriveNcaHeaderKey ( void )
{
Result rc = 0 ;
2021-05-11 07:00:33 +01:00
/* Derive nca_header_kek_sealed from nca_header_kek_source. */
rc = splCryptoGenerateAesKek ( g_ncaKeyset . nca_header_kek_source , 0 , 0 , g_ncaKeyset . nca_header_kek_sealed ) ;
2020-04-11 06:28:26 +01:00
if ( R_FAILED ( rc ) )
{
2021-05-11 07:00:33 +01:00
LOG_MSG ( " splCryptoGenerateAesKek failed! (0x%08X) (nca_header_kek_sealed). " , rc ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
2021-05-11 07:00:33 +01:00
/* Derive nca_header_key from nca_header_kek_sealed and nca_header_key_source. */
rc = splCryptoGenerateAesKey ( g_ncaKeyset . nca_header_kek_sealed , g_ncaKeyset . nca_header_key_source , g_ncaKeyset . nca_header_key ) ;
2020-04-11 06:28:26 +01:00
if ( R_FAILED ( rc ) )
{
2021-05-11 07:00:33 +01:00
LOG_MSG ( " splCryptoGenerateAesKey failed! (0x%08X) (nca_header_key, part 1). " , rc ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
2021-05-11 07:00:33 +01:00
rc = splCryptoGenerateAesKey ( g_ncaKeyset . nca_header_kek_sealed , g_ncaKeyset . nca_header_key_source + AES_128_KEY_SIZE , g_ncaKeyset . nca_header_key + AES_128_KEY_SIZE ) ;
2020-04-11 06:28:26 +01:00
if ( R_FAILED ( rc ) )
{
2021-05-11 07:00:33 +01:00
LOG_MSG ( " splCryptoGenerateAesKey failed! (0x%08X) (nca_header_key, part 2). " , rc ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
return true ;
}
2021-05-11 07:00:33 +01:00
static bool keysDeriveSealedNcaKeyAreaEncryptionKeys ( void )
{
Result rc = 0 ;
u32 key_cnt = 0 ;
u8 highest_key_gen = 0 ;
bool success = false ;
for ( u8 i = 0 ; i < NcaKeyAreaEncryptionKeyIndex_Count ; i + + )
{
/* Get pointer to current KAEK source. */
const u8 * nca_kaek_source = ( const u8 * ) ( g_ncaKeyset . nca_kaek_sources [ i ] ) ;
for ( u8 j = 1 ; j < = NcaKeyGeneration_Max ; j + + )
{
/* Get pointer to current sealed KAEK. */
u8 key_gen_val = ( j - 1 ) ;
u8 * nca_kaek_sealed = g_ncaKeyset . nca_kaek_sealed [ i ] [ key_gen_val ] ;
/* Derive sealed KAEK using the current KAEK source and key generation. */
rc = splCryptoGenerateAesKek ( nca_kaek_source , j , 0 , nca_kaek_sealed ) ;
if ( R_FAILED ( rc ) )
{
//LOG_MSG("splCryptoGenerateAesKek failed for KAEK index %u and key generation %u! (0x%08X).", i, (j <= 1 ? 0 : j), rc);
break ;
}
/* Update derived key count and highest key generation value. */
key_cnt + + ;
if ( key_gen_val > highest_key_gen ) highest_key_gen = key_gen_val ;
}
}
success = ( key_cnt > 0 ) ;
if ( success ) LOG_MSG ( " Derived %u sealed NCA KAEK(s) (%u key generation[s]). " , key_cnt , highest_key_gen + 1 ) ;
return success ;
}
2020-04-11 06:28:26 +01:00
/**
* Reads a line from file f and parses out the key and value from it .
2022-04-18 22:38:18 +01:00
* The format of a line must match / ^ [ \ t ] * \ w + [ \ t ] * [ , = ] [ \ t ] * ( ? : [ A - Fa - f0 - 9 ] { 2 } ) + [ \ t ] * $ / .
2020-04-11 06:28:26 +01:00
* If a line ends in \ r , the final \ r is stripped .
* The input file is assumed to have been opened with the ' b ' flag .
* The input file is assumed to contain only ASCII .
*
2022-04-18 22:38:18 +01:00
* On success , * line will point to a dynamically allocated buffer that holds
* the read line , whilst * key and * value will be set to point to the key and
* value strings within * line , respectively . * line must be freed by the caller .
* On failure , * line , * key and * value will all be set to NULL .
* Empty lines and end of file are both considered failures .
2020-04-11 06:28:26 +01:00
*
2022-04-18 22:38:18 +01:00
* This function is thread - safe .
2020-04-11 06:28:26 +01:00
*
2022-04-18 22:38:18 +01:00
* Both key and value strings will be converted to lowercase .
* Empty key and / or value strings are both considered a parse error .
* Furthermore , a parse error will also be returned if the value string length
* is not a multiple of 2.
2020-04-11 06:28:26 +01:00
*
* This function assumes that the file can be trusted not to contain any NUL in
* the contents .
*
* Whitespace ( ' ' , ASCII 0x20 , as well as ' \t ' , ASCII 0x09 ) at the beginning of
* the line , at the end of the line as well as around = ( or , ) will be ignored .
*
* @ param f the file to read
2022-04-18 22:38:18 +01:00
* @ param line pointer to change to point to the read line
2020-04-11 06:28:26 +01:00
* @ param key pointer to change to point to the key
* @ param value pointer to change to point to the value
* @ return 0 on success ,
* 1 on end of file ,
2022-04-18 22:38:18 +01:00
* - 1 on parse error ( line malformed , empty line )
2020-04-11 06:28:26 +01:00
* - 2 on I / O error
*/
2022-04-18 22:38:18 +01:00
static int keysGetKeyAndValueFromFile ( FILE * f , char * * line , char * * key , char * * value )
2020-04-11 06:28:26 +01:00
{
2022-04-18 22:38:18 +01:00
if ( ! f | | ! line | | ! key | | ! value )
2020-04-11 06:28:26 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-04-11 06:28:26 +01:00
return - 2 ;
}
2022-04-18 22:38:18 +01:00
int ret = - 1 ;
size_t n = 0 ;
ssize_t read = 0 ;
char * l = NULL , * k = NULL , * v = NULL , * p = NULL , * e = NULL ;
2020-04-11 06:28:26 +01:00
2022-04-18 22:38:18 +01:00
/* Clear inputs beforehand. */
if ( * line ) free ( * line ) ;
* line = * key = * value = NULL ;
2020-04-11 06:28:26 +01:00
errno = 0 ;
2022-04-18 22:38:18 +01:00
/* Read line. */
read = __getline ( line , & n , f ) ;
if ( errno ! = 0 | | read < = 0 )
2020-04-11 06:28:26 +01:00
{
2022-04-18 22:38:18 +01:00
ret = ( ( errno = = 0 & & ( read = = 0 | | feof ( f ) ) ) ? 1 : - 2 ) ;
if ( ret ! = 1 ) LOG_MSG ( " __getline failed! (0x%lX, %ld, %d, %d). " , ftell ( f ) , read , errno , ret ) ;
goto end ;
2020-04-11 06:28:26 +01:00
}
2022-04-18 22:38:18 +01:00
n = ( ftell ( f ) - ( size_t ) read ) ;
2020-04-11 06:28:26 +01:00
2022-04-18 22:38:18 +01:00
/* Check if we're dealing with an empty line. */
l = * line ;
if ( * l = = ' \n ' | | * l = = ' \r ' | | * l = = ' \0 ' )
{
LOG_MSG ( " Empty line detected! (0x%lX, 0x%lX). " , n , read ) ;
goto end ;
}
2020-04-11 06:28:26 +01:00
2022-04-18 22:38:18 +01:00
/* Not finding '\r' or '\n' is not a problem. */
/* It's possible that the last line of a file isn't actually a line (i.e., does not end in '\n'). */
/* We do want to handle those. */
if ( ( p = strchr ( l , ' \r ' ) ) ! = NULL | | ( p = strchr ( l , ' \n ' ) ) ! = NULL )
2020-04-11 06:28:26 +01:00
{
2022-04-18 22:38:18 +01:00
e = p ;
2020-04-11 06:28:26 +01:00
* p = ' \0 ' ;
} else {
2022-04-18 22:38:18 +01:00
e = ( l + read + 1 ) ;
2020-04-11 06:28:26 +01:00
}
2022-04-18 22:38:18 +01:00
# define SKIP_SPACE(p) do { \
for ( ; ( * p = = ' ' | | * p = = ' \t ' ) ; + + p ) ; \
} while ( 0 ) ;
/* Skip leading whitespace before the key name string. */
p = l ;
2020-04-11 06:28:26 +01:00
SKIP_SPACE ( p ) ;
k = p ;
2022-04-18 22:38:18 +01:00
/* Validate key name string. */
for ( ; * p ! = ' ' & & * p ! = ' \t ' & & * p ! = ' , ' & & * p ! = ' = ' ; + + p )
2020-04-11 06:28:26 +01:00
{
2022-04-18 22:38:18 +01:00
/* Bail out if we reached the end of string. */
if ( * p = = ' \0 ' )
{
LOG_MSG ( " End of string reached while validating key name string! (#1) (0x%lX, 0x%lX, 0x%lX). " , n , read , ( size_t ) ( p - l ) ) ;
goto end ;
}
2020-04-11 06:28:26 +01:00
2022-04-18 22:38:18 +01:00
/* Convert uppercase characters to lowercase. */
2020-04-11 06:28:26 +01:00
if ( * p > = ' A ' & & * p < = ' Z ' )
{
2022-04-18 22:38:18 +01:00
* p = ( ' a ' + ( * p - ' A ' ) ) ;
2020-04-11 06:28:26 +01:00
continue ;
}
2022-04-18 22:38:18 +01:00
/* Handle unsupported characters. */
if ( * p ! = ' _ ' & & ( * p < ' 0 ' | | * p > ' 9 ' ) & & ( * p < ' a ' | | * p > ' z ' ) )
{
LOG_MSG ( " Unsupported character detected in key name string! (0x%lX, 0x%lX, 0x%lX, 0x%02X). " , n , read , ( size_t ) ( p - l ) , * p ) ;
goto end ;
}
2020-04-11 06:28:26 +01:00
}
2020-07-06 01:10:07 +01:00
/* Bail if the final ++p put us at the end of string. */
2022-04-18 22:38:18 +01:00
if ( * p = = ' \0 ' )
{
LOG_MSG ( " End of string reached while validating key name string! (#2) (0x%lX, 0x%lX, 0x%lX). " , n , read , ( size_t ) ( p - l ) ) ;
goto end ;
}
2020-04-11 06:28:26 +01:00
2022-04-18 22:38:18 +01:00
/* We should be at the end of the key name string now and either whitespace or [,=] follows. */
2020-04-11 06:28:26 +01:00
if ( * p = = ' = ' | | * p = = ' , ' )
{
* p + + = ' \0 ' ;
} else {
2022-04-18 22:38:18 +01:00
/* Skip leading whitespace before [,=]. */
2020-04-11 06:28:26 +01:00
* p + + = ' \0 ' ;
SKIP_SPACE ( p ) ;
2022-04-18 22:38:18 +01:00
if ( * p ! = ' = ' & & * p ! = ' , ' )
{
LOG_MSG ( " Unable to find expected [,=]! (0x%lX, 0x%lX, 0x%lX). " , n , read , ( size_t ) ( p - l ) ) ;
goto end ;
}
2020-04-11 06:28:26 +01:00
* p + + = ' \0 ' ;
}
2022-04-18 22:38:18 +01:00
/* Empty key name string is an error. */
if ( * k = = ' \0 ' )
{
LOG_MSG ( " Key name string empty! (0x%lX, 0x%lX). " , n , read ) ;
goto end ;
}
2020-04-11 06:28:26 +01:00
2022-04-18 22:38:18 +01:00
/* Skip trailing whitespace after [,=]. */
2020-04-11 06:28:26 +01:00
SKIP_SPACE ( p ) ;
v = p ;
2022-04-18 22:38:18 +01:00
# undef SKIP_SPACE
2020-04-11 06:28:26 +01:00
2022-04-18 22:38:18 +01:00
/* Validate value string. */
for ( ; p < e & & * p ! = ' ' & & * p ! = ' \t ' ; + + p )
{
/* Bail out if we reached the end of string. */
if ( * p = = ' \0 ' )
{
LOG_MSG ( " End of string reached while validating value string! (0x%lX, 0x%lX, 0x%lX, %s). " , n , read , ( size_t ) ( p - l ) , k ) ;
goto end ;
}
/* Convert uppercase characters to lowercase. */
if ( * p > = ' A ' & & * p < = ' F ' )
{
* p = ( ' a ' + ( * p - ' A ' ) ) ;
continue ;
}
/* Handle unsupported characters. */
if ( ( * p < ' 0 ' | | * p > ' 9 ' ) & & ( * p < ' a ' | | * p > ' f ' ) )
{
LOG_MSG ( " Unsupported character detected in value string! (0x%lX, 0x%lX, 0x%lX, 0x%02X, %s). " , n , read , ( size_t ) ( p - l ) , * p , k ) ;
goto end ;
}
}
2020-04-11 06:28:26 +01:00
2022-04-18 22:38:18 +01:00
/* We should be at the end of the value string now and whitespace may optionally follow. */
l = p ;
if ( p < e )
{
/* Skip trailing whitespace after the value string. */
/* Make sure there's no additional data after this. */
* p + + = ' \0 ' ;
for ( ; p < e & & ( * p = = ' ' | | * p = = ' \t ' ) ; + + p ) ;
if ( p < e )
{
LOG_MSG ( " Additional data detected after value string and before line end! (0x%lX, 0x%lX, 0x%lX, %s). " , n , read , ( size_t ) ( p - * line ) , k ) ;
goto end ;
}
}
/* Empty value string and value string length not being a multiple of 2 are both errors. */
if ( * v = = ' \0 ' | | ( ( l - v ) % 2 ) ! = 0 )
{
LOG_MSG ( " Invalid value string length! (0x%lX, 0x%lX, 0x%lX, %s). " , n , read , ( size_t ) ( l - v ) , k ) ;
goto end ;
}
/* Update pointers. */
2020-04-11 06:28:26 +01:00
* key = k ;
* value = v ;
2022-04-18 22:38:18 +01:00
/* Update return value. */
ret = 0 ;
2020-04-11 06:28:26 +01:00
2022-04-18 22:38:18 +01:00
end :
if ( ret ! = 0 )
{
if ( * line ) free ( * line ) ;
* line = * key = * value = NULL ;
}
return ret ;
2020-04-11 06:28:26 +01:00
}
2022-04-18 22:38:18 +01:00
static char keysConvertHexDigitToBinary ( char c )
2020-04-11 06:28:26 +01:00
{
if ( ' a ' < = c & & c < = ' f ' ) return ( c - ' a ' + 0xA ) ;
if ( ' A ' < = c & & c < = ' F ' ) return ( c - ' A ' + 0xA ) ;
if ( ' 0 ' < = c & & c < = ' 9 ' ) return ( c - ' 0 ' ) ;
return ' z ' ;
}
static bool keysParseHexKey ( u8 * out , const char * key , const char * value , u32 size )
{
2020-07-03 10:31:22 +01:00
u32 hex_str_len = ( 2 * size ) ;
size_t value_len = 0 ;
2020-10-15 01:06:53 +01:00
if ( ! out | | ! key | | ! * key | | ! value | | ! ( value_len = strlen ( value ) ) | | ! size )
2020-04-11 06:28:26 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
2020-07-03 10:31:22 +01:00
if ( value_len ! = hex_str_len )
2020-04-11 06:28:26 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Key \" %s \" must be %u hex digits long! " , key , hex_str_len ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
memset ( out , 0 , size ) ;
for ( u32 i = 0 ; i < hex_str_len ; i + + )
{
2022-04-18 22:38:18 +01:00
char val = keysConvertHexDigitToBinary ( value [ i ] ) ;
2020-04-11 06:28:26 +01:00
if ( val = = ' z ' )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid hex character in key \" %s \" at position %u! " , key , i ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
if ( ( i & 1 ) = = 0 ) val < < = 4 ;
out [ i > > 1 ] | = val ;
}
return true ;
}
static bool keysReadKeysFromFile ( void )
{
int ret = 0 ;
u32 key_count = 0 ;
FILE * keys_file = NULL ;
2022-04-18 22:38:18 +01:00
char * line = NULL , * key = NULL , * value = NULL ;
2020-04-11 06:28:26 +01:00
char test_name [ 0x40 ] = { 0 } ;
2022-04-18 22:38:18 +01:00
bool eticket_rsa_kek_available = false ;
2022-02-10 18:05:07 +00:00
const char * keys_file_path = ( utilsIsDevelopmentUnit ( ) ? DEV_KEYS_FILE_PATH : PROD_KEYS_FILE_PATH ) ;
2020-04-11 06:28:26 +01:00
2022-02-10 18:05:07 +00:00
keys_file = fopen ( keys_file_path , " rb " ) ;
2020-04-11 06:28:26 +01:00
if ( ! keys_file )
{
2022-02-10 18:05:07 +00:00
LOG_MSG ( " Unable to open \" %s \" to retrieve keys! " , keys_file_path ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
2022-04-18 22:38:18 +01:00
# define PARSE_HEX_KEY(name, out, decl) \
if ( ! strcmp ( key , name ) & & keysParseHexKey ( out , key , value , sizeof ( out ) ) ) { \
key_count + + ; \
decl ; \
}
# define PARSE_HEX_KEY_WITH_INDEX(name, out) \
snprintf ( test_name , sizeof ( test_name ) , " %s_%02x " , name , i ) ; \
PARSE_HEX_KEY ( test_name , out , break ) ;
2020-04-11 06:28:26 +01:00
while ( true )
{
2022-04-18 22:38:18 +01:00
/* Get key and value strings from the current line. */
/* Break from the while loop if EOF is reached or if an I/O error occurs. */
ret = keysGetKeyAndValueFromFile ( keys_file , & line , & key , & value ) ;
if ( ret = = 1 | | ret = = - 2 ) break ;
2020-04-11 06:28:26 +01:00
2022-04-18 22:38:18 +01:00
/* Ignore malformed or empty lines. */
2020-04-11 06:28:26 +01:00
if ( ret ! = 0 | | ! key | | ! value ) continue ;
2022-04-18 22:38:18 +01:00
PARSE_HEX_KEY ( " eticket_rsa_kek " , g_ncaKeyset . eticket_rsa_kek , eticket_rsa_kek_available = true ; continue ) ;
/* This only appears on consoles that use the new PRODINFO key generation scheme. */
PARSE_HEX_KEY ( " eticket_rsa_kek_personalized " , g_ncaKeyset . eticket_rsa_kek_personalized , eticket_rsa_kek_available = true ; continue ) ;
for ( u32 i = 0 ; i < NcaKeyGeneration_Max ; i + + )
2020-04-11 06:28:26 +01:00
{
2022-04-18 22:38:18 +01:00
PARSE_HEX_KEY_WITH_INDEX ( " titlekek " , g_ncaKeyset . ticket_common_keys [ i ] ) ;
PARSE_HEX_KEY_WITH_INDEX ( " key_area_key_application " , g_ncaKeyset . nca_kaek [ NcaKeyAreaEncryptionKeyIndex_Application ] [ i ] ) ;
PARSE_HEX_KEY_WITH_INDEX ( " key_area_key_ocean " , g_ncaKeyset . nca_kaek [ NcaKeyAreaEncryptionKeyIndex_Ocean ] [ i ] ) ;
2020-07-03 10:31:22 +01:00
2022-04-18 22:38:18 +01:00
PARSE_HEX_KEY_WITH_INDEX ( " key_area_key_system " , g_ncaKeyset . nca_kaek [ NcaKeyAreaEncryptionKeyIndex_System ] [ i ] ) ;
2020-04-11 06:28:26 +01:00
}
}
2022-04-18 22:38:18 +01:00
# undef PARSE_HEX_KEY_WITH_INDEX
# undef PARSE_HEX_KEY
if ( line ) free ( line ) ;
2020-04-11 06:28:26 +01:00
fclose ( keys_file ) ;
2022-04-18 22:38:18 +01:00
if ( key_count )
2020-04-11 06:28:26 +01:00
{
2022-04-18 22:38:18 +01:00
LOG_MSG ( " Loaded %u key(s) from \" %s \" . " , key_count , keys_file_path ) ;
} else {
LOG_MSG ( " Unable to parse keys from \" %s \" ! (keys file empty?). " , keys_file_path ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
2020-10-21 05:27:48 +01:00
if ( ! eticket_rsa_kek_available )
{
2022-02-10 18:05:07 +00:00
LOG_MSG ( " \" eticket_rsa_kek \" unavailable in \" %s \" ! " , keys_file_path ) ;
2020-10-21 05:27:48 +01:00
return false ;
}
2020-04-11 06:28:26 +01:00
return true ;
}
2021-05-11 07:00:33 +01:00
static bool keysGetDecryptedEticketRsaDeviceKey ( void )
{
Result rc = 0 ;
u32 public_exponent = 0 ;
const u8 * eticket_rsa_kek = NULL ;
EticketRsaDeviceKey * eticket_rsa_key = NULL ;
Aes128CtrContext eticket_aes_ctx = { 0 } ;
/* Get eTicket RSA device key. */
rc = setcalGetEticketDeviceKey ( & g_eTicketRsaDeviceKey ) ;
if ( R_FAILED ( rc ) )
{
LOG_MSG ( " setcalGetEticketDeviceKey failed! (0x%08X). " , rc ) ;
return false ;
}
/* Get eTicket RSA device key encryption key. */
eticket_rsa_kek = ( const u8 * ) ( g_eTicketRsaDeviceKey . generation > 0 ? g_ncaKeyset . eticket_rsa_kek_personalized : g_ncaKeyset . eticket_rsa_kek ) ;
/* Decrypt eTicket RSA device key. */
eticket_rsa_key = ( EticketRsaDeviceKey * ) g_eTicketRsaDeviceKey . key ;
aes128CtrContextCreate ( & eticket_aes_ctx , eticket_rsa_kek , eticket_rsa_key - > ctr ) ;
2021-05-21 14:34:43 +01:00
aes128CtrCrypt ( & eticket_aes_ctx , & ( eticket_rsa_key - > private_exponent ) , & ( eticket_rsa_key - > private_exponent ) , sizeof ( EticketRsaDeviceKey ) - sizeof ( eticket_rsa_key - > ctr ) ) ;
2021-05-11 07:00:33 +01:00
/* Public exponent value must be 0x10001. */
/* It is stored using big endian byte order. */
public_exponent = __builtin_bswap32 ( eticket_rsa_key - > public_exponent ) ;
if ( public_exponent ! = ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT )
{
LOG_MSG ( " Invalid public exponent for decrypted eTicket RSA device key! Wrong keys? (0x%08X). " , public_exponent ) ;
return false ;
}
/* Test RSA key pair. */
2021-05-21 14:34:43 +01:00
if ( ! keysTestEticketRsaDeviceKey ( & ( eticket_rsa_key - > public_exponent ) , eticket_rsa_key - > private_exponent , eticket_rsa_key - > modulus ) )
2021-05-11 07:00:33 +01:00
{
LOG_MSG ( " eTicket RSA device key test failed! Wrong keys? " ) ;
return false ;
}
return true ;
}
static bool keysTestEticketRsaDeviceKey ( const void * e , const void * d , const void * n )
{
if ( ! e | | ! d | | ! n )
{
LOG_MSG ( " Invalid parameters! " ) ;
return false ;
}
Result rc = 0 ;
2021-05-21 14:34:43 +01:00
u8 x [ RSA2048_BYTES ] = { 0 } , y [ RSA2048_BYTES ] = { 0 } , z [ RSA2048_BYTES ] = { 0 } ;
2021-05-11 07:00:33 +01:00
/* 0xCAFEBABE. */
x [ 0xFC ] = 0xCA ;
x [ 0xFD ] = 0xFE ;
x [ 0xFE ] = 0xBA ;
x [ 0xFF ] = 0xBE ;
2021-05-21 14:34:43 +01:00
rc = splUserExpMod ( x , n , d , RSA2048_BYTES , y ) ;
2021-05-11 07:00:33 +01:00
if ( R_FAILED ( rc ) )
{
LOG_MSG ( " splUserExpMod failed! (#1) (0x%08X). " , rc ) ;
return false ;
}
rc = splUserExpMod ( y , n , e , 4 , z ) ;
if ( R_FAILED ( rc ) )
{
LOG_MSG ( " splUserExpMod failed! (#2) (0x%08X). " , rc ) ;
return false ;
}
2021-05-21 14:34:43 +01:00
if ( memcmp ( x , z , RSA2048_BYTES ) ! = 0 )
2021-05-11 07:00:33 +01:00
{
LOG_MSG ( " Invalid RSA key pair! " ) ;
return false ;
}
return true ;
}
2021-05-22 09:45:40 +01:00
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 ;
}