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 .
2020-12-23 17:48:57 +00:00
* Copyright ( c ) 2020 - 2021 , 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 ) ;
static int keysGetKeyAndValueFromFile ( FILE * f , char * * key , char * * value ) ;
static char keysConvertHexCharToBinary ( char c ) ;
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
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 ) ;
2021-05-18 13:32:43 +01:00
if ( mandatory ) 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 ) ;
2021-05-18 13:32:43 +01:00
if ( mandatory ) 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 .
* The format of a line must match / ^ * [ A - Za - z0 - 9 _ ] * [ , = ] * . + $ / .
* 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 .
*
* A line cannot exceed 512 bytes in length .
* Lines that are excessively long will be silently truncated .
*
* On success , * key and * value will be set to point to the key and value in
* the input line , respectively .
* * key and * value may also be NULL in case of empty lines .
* On failure , * key and * value will be set to NULL .
* End of file is considered failure .
*
* Because * key and * value will point to a static buffer , their contents must be
* copied before calling this function again .
* For the same reason , this function is not thread - safe .
*
* The key will be converted to lowercase .
* An empty key is considered a parse error , but an empty value is returned as
* success .
*
* 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
* @ 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 ,
* - 1 on parse error ( line too long , line malformed )
* - 2 on I / O error
*/
static int keysGetKeyAndValueFromFile ( FILE * f , char * * key , char * * value )
{
if ( ! f | | ! key | | ! value )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-04-11 06:28:26 +01:00
return - 2 ;
}
# define SKIP_SPACE(p) do {\
for ( ; ( * p = = ' ' | | * p = = ' \t ' ) ; + + p ) ; \
} while ( 0 ) ;
static char line [ 512 ] = { 0 } ;
char * k , * v , * p , * end ;
* key = * value = NULL ;
errno = 0 ;
if ( fgets ( line , ( int ) sizeof ( line ) , f ) = = NULL )
{
if ( feof ( f ) )
{
return 1 ;
} else {
return - 2 ;
}
}
if ( errno ! = 0 ) return - 2 ;
if ( * line = = ' \n ' | | * line = = ' \r ' | | * line = = ' \0 ' ) return 0 ;
/* Not finding \r or \n is not a problem.
* The line might just be exactly 512 characters long , we have no way to
* tell .
* Additionally , it ' s possible that the last line of a file is not actually
* a line ( i . e . , does not end in ' \n ' ) ; we do want to handle those .
*/
if ( ( p = strchr ( line , ' \r ' ) ) ! = NULL | | ( p = strchr ( line , ' \n ' ) ) ! = NULL )
{
end = p ;
* p = ' \0 ' ;
} else {
end = ( line + strlen ( line ) + 1 ) ;
}
p = line ;
SKIP_SPACE ( p ) ;
k = p ;
/* Validate key and convert to lower case. */
for ( ; * p ! = ' ' & & * p ! = ' , ' & & * p ! = ' \t ' & & * p ! = ' = ' ; + + p )
{
if ( * p = = ' \0 ' ) return - 1 ;
if ( * p > = ' A ' & & * p < = ' Z ' )
{
* p = ' a ' + ( * p - ' A ' ) ;
continue ;
}
if ( * p ! = ' _ ' & & ( * p < ' 0 ' & & * p > ' 9 ' ) & & ( * p < ' a ' & & * p > ' z ' ) ) return - 1 ;
}
2020-07-06 01:10:07 +01:00
/* Bail if the final ++p put us at the end of string. */
2020-04-11 06:28:26 +01:00
if ( * p = = ' \0 ' ) return - 1 ;
2020-07-06 01:10:07 +01:00
/* We should be at the end of key now and either whitespace or [,=] follows. */
2020-04-11 06:28:26 +01:00
if ( * p = = ' = ' | | * p = = ' , ' )
{
* p + + = ' \0 ' ;
} else {
* p + + = ' \0 ' ;
SKIP_SPACE ( p ) ;
if ( * p ! = ' = ' & & * p ! = ' , ' ) return - 1 ;
* p + + = ' \0 ' ;
}
/* Empty key is an error. */
if ( * k = = ' \0 ' ) return - 1 ;
SKIP_SPACE ( p ) ;
v = p ;
2020-07-06 01:10:07 +01:00
/* Skip trailing whitespace. */
2020-04-11 06:28:26 +01:00
for ( p = end - 1 ; * p = = ' \t ' | | * p = = ' ' ; - - p ) ;
* ( p + 1 ) = ' \0 ' ;
* key = k ;
* value = v ;
return 0 ;
# undef SKIP_SPACE
}
static char keysConvertHexCharToBinary ( char c )
{
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 + + )
{
char val = keysConvertHexCharToBinary ( value [ i ] ) ;
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 ;
char * key = NULL , * value = NULL ;
char test_name [ 0x40 ] = { 0 } ;
2020-10-21 05:27:48 +01:00
bool parse_fail = false , eticket_rsa_kek_available = false ;
2020-04-11 06:28:26 +01:00
keys_file = fopen ( KEYS_FILE_PATH , " rb " ) ;
if ( ! keys_file )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Unable to open \" %s \" to retrieve keys! " , KEYS_FILE_PATH ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
while ( true )
{
ret = keysGetKeyAndValueFromFile ( keys_file , & key , & value ) ;
2021-05-11 07:00:33 +01:00
if ( ret = = 1 | | ret = = - 2 ) break ; /* Break from the while loop if EOF is reached or if an I/O error occurs. */
2020-04-11 06:28:26 +01:00
2020-07-06 01:10:07 +01:00
/* Ignore malformed lines. */
2020-04-11 06:28:26 +01:00
if ( ret ! = 0 | | ! key | | ! value ) continue ;
2021-05-11 07:00:33 +01:00
if ( ! strcasecmp ( key , " eticket_rsa_kek " ) )
2020-04-11 06:28:26 +01:00
{
2020-07-03 10:31:22 +01:00
if ( ( parse_fail = ! keysParseHexKey ( g_ncaKeyset . eticket_rsa_kek , key , value , sizeof ( g_ncaKeyset . eticket_rsa_kek ) ) ) ) break ;
2020-10-21 05:27:48 +01:00
eticket_rsa_kek_available = true ;
2020-04-11 06:28:26 +01:00
key_count + + ;
} else
2021-05-11 07:00:33 +01:00
if ( ! strcasecmp ( key , " eticket_rsa_kek_personalized " ) )
2020-04-11 06:28:26 +01:00
{
2020-07-06 01:10:07 +01:00
/* This only appears on consoles that use the new PRODINFO key generation scheme. */
2020-07-07 13:58:17 +01:00
if ( ( parse_fail = ! keysParseHexKey ( g_ncaKeyset . eticket_rsa_kek_personalized , key , value , sizeof ( g_ncaKeyset . eticket_rsa_kek_personalized ) ) ) ) break ;
2020-10-21 05:27:48 +01:00
eticket_rsa_kek_available = true ;
2020-04-11 06:28:26 +01:00
key_count + + ;
} else {
2021-05-11 07:00:33 +01:00
for ( u32 i = 0 ; i < NcaKeyGeneration_Max ; i + + )
2020-04-11 06:28:26 +01:00
{
snprintf ( test_name , sizeof ( test_name ) , " titlekek_%02x " , i ) ;
2021-05-11 07:00:33 +01:00
if ( ! strcasecmp ( key , test_name ) )
2020-04-11 06:28:26 +01:00
{
2021-05-11 07:00:33 +01:00
if ( ( parse_fail = ! keysParseHexKey ( g_ncaKeyset . ticket_common_keys [ i ] , key , value , sizeof ( g_ncaKeyset . ticket_common_keys [ i ] ) ) ) ) break ;
2020-04-11 06:28:26 +01:00
key_count + + ;
break ;
}
snprintf ( test_name , sizeof ( test_name ) , " key_area_key_application_%02x " , i ) ;
2021-05-11 07:00:33 +01:00
if ( ! strcasecmp ( key , test_name ) )
2020-04-11 06:28:26 +01:00
{
2021-05-11 07:00:33 +01:00
if ( ( parse_fail = ! keysParseHexKey ( g_ncaKeyset . nca_kaek [ NcaKeyAreaEncryptionKeyIndex_Application ] [ i ] , key , value , \
sizeof ( g_ncaKeyset . nca_kaek [ NcaKeyAreaEncryptionKeyIndex_Application ] [ i ] ) ) ) ) break ;
2020-04-11 06:28:26 +01:00
key_count + + ;
break ;
}
snprintf ( test_name , sizeof ( test_name ) , " key_area_key_ocean_%02x " , i ) ;
2021-05-11 07:00:33 +01:00
if ( ! strcasecmp ( key , test_name ) )
2020-04-11 06:28:26 +01:00
{
2021-05-11 07:00:33 +01:00
if ( ( parse_fail = ! keysParseHexKey ( g_ncaKeyset . nca_kaek [ NcaKeyAreaEncryptionKeyIndex_Ocean ] [ i ] , key , value , \
sizeof ( g_ncaKeyset . nca_kaek [ NcaKeyAreaEncryptionKeyIndex_Ocean ] [ i ] ) ) ) ) break ;
2020-04-11 06:28:26 +01:00
key_count + + ;
break ;
}
snprintf ( test_name , sizeof ( test_name ) , " key_area_key_system_%02x " , i ) ;
2021-05-11 07:00:33 +01:00
if ( ! strcasecmp ( key , test_name ) )
2020-04-11 06:28:26 +01:00
{
2021-05-11 07:00:33 +01:00
if ( ( parse_fail = ! keysParseHexKey ( g_ncaKeyset . nca_kaek [ NcaKeyAreaEncryptionKeyIndex_System ] [ i ] , key , value , \
sizeof ( g_ncaKeyset . nca_kaek [ NcaKeyAreaEncryptionKeyIndex_System ] [ i ] ) ) ) ) break ;
2020-04-11 06:28:26 +01:00
key_count + + ;
break ;
}
}
2020-07-03 10:31:22 +01:00
if ( parse_fail ) break ;
2020-04-11 06:28:26 +01:00
}
}
fclose ( keys_file ) ;
2020-07-03 10:31:22 +01:00
if ( parse_fail | | ! key_count )
2020-04-11 06:28:26 +01:00
{
2021-03-07 23:22:49 +00:00
if ( ! key_count ) LOG_MSG ( " Unable to parse necessary 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 )
{
2021-03-07 23:22:49 +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 ;
}