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 .
2023-04-08 12:42:22 +01:00
* Copyright ( c ) 2020 - 2023 , 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"
# include "nca.h"
2021-05-11 07:00:33 +01:00
# include "rsa.h"
2023-04-08 12:34:53 +01:00
# include "aes.h"
# include "smc.h"
# include "key_sources.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. */
2020-04-11 06:28:26 +01:00
typedef struct {
2023-04-08 12:34:53 +01:00
///< AES-128-ECB key used to derive master KEKs from Erista master KEK sources.
///< Only available in Erista units. Retrieved from the Lockpick_RCM keys file.
u8 tsec_root_key [ AES_128_KEY_SIZE ] ;
2020-04-11 06:28:26 +01:00
2023-04-08 12:34:53 +01:00
///< AES-128-ECB key used to derive master KEKs from Mariko master KEK sources.
///< Only available in Mariko units. Retrieved from the Lockpick_RCM keys file -- if available, because it must be manually bruteforced on a PC after running Lockpick_RCM.
u8 mariko_kek [ AES_128_KEY_SIZE ] ;
2020-04-11 06:28:26 +01:00
2023-04-08 12:34:53 +01:00
///< AES-128-ECB keys used to decrypt the vast majority of Switch content.
///< Derived at runtime using hardcoded key sources and additional keydata retrieved from the Lockpick_RCM keys file.
u8 master_keys [ NcaKeyGeneration_Max ] [ AES_128_KEY_SIZE ] ;
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
///< AES-128-XTS key needed to handle NCA header crypto.
///< Generated from hardcoded key sources.
u8 nca_header_key [ AES_128_KEY_SIZE * 2 ] ;
2022-07-05 02:04:28 +01:00
2021-05-11 07:00:33 +01:00
///< AES-128-ECB keys needed to handle key area crypto from NCA headers.
2023-04-08 12:34:53 +01:00
///< Generated from hardcoded key sources and master keys.
u8 nca_kaek [ NcaKeyAreaEncryptionKeyIndex_Count ] [ NcaKeyGeneration_Max ] [ AES_128_KEY_SIZE ] ;
2022-07-05 02:04:28 +01:00
2021-05-11 07:00:33 +01:00
///< AES-128-CTR key needed to decrypt the console-specific eTicket RSA device key stored in PRODINFO.
2023-04-08 12:34:53 +01:00
///< Retrieved from the Lockpick_RCM keys file. Verified by decrypting the eTicket RSA device key.
///< The key itself may or may not be console-specific (personalized), based on the eTicket RSA device key generation value.
u8 eticket_rsa_kek [ AES_128_KEY_SIZE ] ;
2022-07-05 02:04:28 +01:00
2021-05-11 07:00:33 +01:00
///< AES-128-ECB keys needed to decrypt titlekeys.
2023-04-08 12:34:53 +01:00
///< Generated from a hardcoded key source and master keys.
u8 ticket_common_keys [ NcaKeyGeneration_Max ] [ AES_128_KEY_SIZE ] ;
///< AES-128-CBC key needed to decrypt the CardInfo area from gamecard headers.
///< Generated from hardcoded key sources.
u8 gc_cardinfo_key [ AES_128_KEY_SIZE ] ;
} KeysNxKeyset ;
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. */
2023-04-08 12:34:53 +01:00
static bool keysIsKeyEmpty ( const void * key ) ;
2021-05-18 13:32:43 +01:00
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 ) ;
2023-04-08 12:34:53 +01:00
static bool keysDeriveMasterKeys ( void ) ;
static bool keysDeriveNcaHeaderKey ( void ) ;
static bool keysDerivePerGenerationKeys ( void ) ;
static bool keysDeriveGcCardInfoKey ( void ) ;
2022-09-13 01:22:15 +01:00
2021-05-18 13:32:43 +01:00
static bool keysGetDecryptedEticketRsaDeviceKey ( void ) ;
static bool keysTestEticketRsaDeviceKey ( const void * e , const void * d , const void * n ) ;
2023-04-08 12:34:53 +01:00
static bool keysGenerateAesKek ( const u8 * kek_src , u8 key_generation , SmcGenerateAesKekOption option , u8 * out_kek ) ;
static bool keysLoadAesKey ( const u8 * kek , const u8 * key_src , u8 * out_key ) ;
static bool keysGenerateAesKey ( const u8 * kek , const u8 * key_src , u8 * out_key ) ;
2021-05-22 09:45:40 +01:00
2023-04-08 12:34:53 +01:00
static bool keysLoadAesKeyFromAesKek ( const u8 * kek_src , u8 key_generation , SmcGenerateAesKekOption option , const u8 * key_src , u8 * out_key ) ;
static bool keysGenerateAesKeyFromAesKek ( const u8 * kek_src , u8 key_generation , SmcGenerateAesKekOption option , const u8 * key_src , u8 * out_key ) ;
2023-03-29 22:14:21 +01:00
2020-04-16 11:13:11 +01:00
/* Global variables. */
2022-09-13 01:22:15 +01:00
static bool g_keysetLoaded = false ;
static Mutex g_keysetMutex = 0 ;
2021-05-11 07:00:33 +01:00
static SetCalRsa2048DeviceKey g_eTicketRsaDeviceKey = { 0 } ;
2023-04-08 12:34:53 +01:00
static KeysNxKeyset g_nxKeyset = { 0 } ;
2021-05-11 07:00:33 +01:00
2023-04-08 12:34:53 +01:00
static bool g_latestMasterKeyAvailable = false ;
2022-09-13 01:22:15 +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 ;
2022-07-05 02:04:28 +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 ;
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Get eTicket RSA device key. */
Result rc = setcalGetEticketDeviceKey ( & g_eTicketRsaDeviceKey ) ;
if ( R_FAILED ( rc ) )
2021-05-18 13:32:43 +01:00
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " setcalGetEticketDeviceKey failed! (0x%X). " , rc ) ;
2021-05-18 13:32:43 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Read data from the Lockpick_RCM keys file. */
if ( ! keysReadKeysFromFile ( ) ) break ;
/* Derive master keys. */
if ( ! keysDeriveMasterKeys ( ) )
2021-05-18 13:32:43 +01:00
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " Failed to derive master keys! " ) ;
2021-05-18 13:32:43 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Derive NCA header key. */
2023-04-08 12:34:53 +01:00
if ( ! keysDeriveNcaHeaderKey ( ) ) break ;
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Derive per-generation keys. */
if ( ! keysDerivePerGenerationKeys ( ) ) break ;
/* Derive gamecard CardInfo key */
if ( ! keysDeriveGcCardInfoKey ( ) )
2021-05-18 13:32:43 +01:00
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " Failed to derive gamecard CardInfo key! " ) ;
2021-05-18 13:32:43 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Get decrypted eTicket RSA device key. */
if ( ! keysGetDecryptedEticketRsaDeviceKey ( ) ) break ;
2022-07-05 02:04:28 +01:00
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
}
2022-07-05 02:04:28 +01:00
2022-07-12 17:34:49 +01:00
# if LOG_LEVEL == LOG_LEVEL_DEBUG
2022-09-13 01:22:15 +01:00
LOG_DATA_DEBUG ( & g_eTicketRsaDeviceKey , sizeof ( SetCalRsa2048DeviceKey ) , " eTicket RSA device key dump: " ) ;
2023-04-08 12:34:53 +01:00
LOG_DATA_DEBUG ( & g_nxKeyset , sizeof ( KeysNxKeyset ) , " NX keyset dump: " ) ;
2022-07-12 17:34:49 +01:00
# endif
2022-07-05 02:04:28 +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 ;
2022-07-05 02:04:28 +01:00
2021-05-22 09:45:40 +01:00
SCOPED_LOCK ( & g_keysetMutex )
2021-05-18 13:32:43 +01:00
{
2023-04-08 12:34:53 +01:00
if ( g_keysetLoaded ) ret = ( const u8 * ) ( g_nxKeyset . nca_header_key ) ;
2021-05-21 14:34:43 +01:00
}
2022-07-05 02:04:28 +01:00
2021-05-21 14:34:43 +01:00
return ret ;
}
2023-04-08 12:34:53 +01:00
const u8 * keysGetNcaKeyAreaEncryptionKey ( u8 kaek_index , u8 key_generation )
2020-04-11 06:28:26 +01:00
{
2023-04-08 12:34:53 +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 ) ;
2022-07-05 02:04:28 +01:00
2021-05-11 07:00:33 +01:00
if ( kaek_index > = NcaKeyAreaEncryptionKeyIndex_Count )
2020-04-11 06:28:26 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid KAEK index! (0x%02X). " , kaek_index ) ;
2021-05-11 07:00:33 +01:00
goto end ;
2020-04-11 06:28:26 +01:00
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
if ( key_generation > = NcaKeyGeneration_Max )
2021-05-11 07:00:33 +01:00
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " Invalid key generation value! (0x%02X). " , key_generation ) ;
2021-05-11 07:00:33 +01:00
goto end ;
}
2022-07-05 02:04:28 +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
if ( ! g_keysetLoaded ) break ;
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
ret = ( const u8 * ) ( g_nxKeyset . nca_kaek [ kaek_index ] [ key_gen_val ] ) ;
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
if ( keysIsKeyEmpty ( ret ) )
{
LOG_MSG_ERROR ( " NCA KAEK for type %u and generation %u unavailable. " , kaek_index , key_gen_val ) ;
ret = NULL ;
}
2021-05-18 13:32:43 +01:00
}
2022-07-05 02:04:28 +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
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2021-05-11 07:00:33 +01:00
return false ;
2020-04-11 06:28:26 +01:00
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
bool ret = false ;
2022-07-05 02:04:28 +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 ;
2022-07-05 02:04:28 +01:00
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 } ;
2022-07-05 02:04:28 +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 ;
2022-07-05 02:04:28 +01:00
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 {
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " RSA-OAEP titlekey decryption failed! " ) ;
2021-05-11 07:00:33 +01:00
}
2020-04-11 06:28:26 +01:00
}
2022-07-05 02:04:28 +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 ) ;
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
if ( key_generation > = NcaKeyGeneration_Max )
2021-05-11 07:00:33 +01:00
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " Invalid key generation value! (0x%02X). " , key_generation ) ;
2021-05-11 07:00:33 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-05-22 09:45:40 +01:00
SCOPED_LOCK ( & g_keysetMutex )
2021-05-18 13:32:43 +01:00
{
2023-04-08 12:34:53 +01:00
if ( ! g_keysetLoaded ) break ;
ret = ( const u8 * ) ( g_nxKeyset . ticket_common_keys [ key_gen_val ] ) ;
if ( keysIsKeyEmpty ( ret ) )
{
LOG_MSG_ERROR ( " Ticket common key for generation %u unavailable. " , key_gen_val ) ;
ret = NULL ;
}
2021-05-18 13:32:43 +01:00
}
2022-07-05 02:04:28 +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 ;
2022-07-05 02:04:28 +01:00
2021-05-22 09:45:40 +01:00
SCOPED_LOCK ( & g_keysetMutex )
{
2023-04-08 12:34:53 +01:00
if ( g_keysetLoaded ) ret = ( const u8 * ) ( g_nxKeyset . gc_cardinfo_key ) ;
2021-05-22 09:45:40 +01:00
}
2022-07-05 02:04:28 +01:00
2021-05-22 09:45:40 +01:00
return ret ;
}
2023-04-08 12:34:53 +01:00
static bool keysIsKeyEmpty ( const void * key )
2021-05-18 13:32:43 +01:00
{
2023-04-08 12:34:53 +01:00
const u8 null_key [ AES_128_KEY_SIZE ] = { 0 } ;
return ( memcmp ( key , null_key , AES_128_KEY_SIZE ) = = 0 ) ;
2021-05-11 07:00:33 +01:00
}
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
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2020-04-11 06:28:26 +01:00
return - 2 ;
}
2022-07-05 02:04:28 +01:00
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 ;
2022-07-05 02:04:28 +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-07-05 02:04:28 +01:00
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 ) ;
2022-07-12 17:34:49 +01:00
if ( ret ! = 1 ) LOG_MSG_ERROR ( " __getline failed! (0x%lX, %ld, %d, %d). " , ftell ( f ) , read , errno , ret ) ;
2022-04-18 22:38:18 +01:00
goto end ;
2020-04-11 06:28:26 +01:00
}
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
n = ( ftell ( f ) - ( size_t ) read ) ;
2022-07-05 02:04:28 +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 ' )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_WARNING ( " Empty line detected! (0x%lX, 0x%lX). " , n , read ) ;
2022-04-18 22:38:18 +01:00
goto end ;
}
2022-07-05 02:04:28 +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-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
# define SKIP_SPACE(p) do { \
for ( ; ( * p = = ' ' | | * p = = ' \t ' ) ; + + p ) ; \
} while ( 0 ) ;
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
/* Skip leading whitespace before the key name string. */
p = l ;
2020-04-11 06:28:26 +01:00
SKIP_SPACE ( p ) ;
k = p ;
2022-07-05 02:04:28 +01:00
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 ' )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " End of string reached while validating key name string! (#1) (0x%lX, 0x%lX, 0x%lX). " , n , read , ( size_t ) ( p - l ) ) ;
2022-04-18 22:38:18 +01:00
goto end ;
}
2022-07-05 02:04:28 +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-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
/* Handle unsupported characters. */
if ( * p ! = ' _ ' & & ( * p < ' 0 ' | | * p > ' 9 ' ) & & ( * p < ' a ' | | * p > ' z ' ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unsupported character detected in key name string! (0x%lX, 0x%lX, 0x%lX, 0x%02X). " , n , read , ( size_t ) ( p - l ) , * p ) ;
2022-04-18 22:38:18 +01:00
goto end ;
}
2020-04-11 06:28:26 +01:00
}
2022-07-05 02:04:28 +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 ' )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " End of string reached while validating key name string! (#2) (0x%lX, 0x%lX, 0x%lX). " , n , read , ( size_t ) ( p - l ) ) ;
2022-04-18 22:38:18 +01:00
goto end ;
}
2022-07-05 02:04:28 +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-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
if ( * p ! = ' = ' & & * p ! = ' , ' )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unable to find expected [,=]! (0x%lX, 0x%lX, 0x%lX). " , n , read , ( size_t ) ( p - l ) ) ;
2022-04-18 22:38:18 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-04-11 06:28:26 +01:00
* p + + = ' \0 ' ;
}
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
/* Empty key name string is an error. */
if ( * k = = ' \0 ' )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Key name string empty! (0x%lX, 0x%lX). " , n , read ) ;
2022-04-18 22:38:18 +01:00
goto end ;
}
2022-07-05 02:04:28 +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-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
# undef SKIP_SPACE
2022-07-05 02:04:28 +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 ' )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " End of string reached while validating value string! (0x%lX, 0x%lX, 0x%lX, %s). " , n , read , ( size_t ) ( p - l ) , k ) ;
2022-04-18 22:38:18 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
/* Convert uppercase characters to lowercase. */
if ( * p > = ' A ' & & * p < = ' F ' )
{
* p = ( ' a ' + ( * p - ' A ' ) ) ;
continue ;
}
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
/* Handle unsupported characters. */
if ( ( * p < ' 0 ' | | * p > ' 9 ' ) & & ( * p < ' a ' | | * p > ' f ' ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unsupported character detected in value string! (0x%lX, 0x%lX, 0x%lX, 0x%02X, %s). " , n , read , ( size_t ) ( p - l ) , * p , k ) ;
2022-04-18 22:38:18 +01:00
goto end ;
}
}
2022-07-05 02:04:28 +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 ) ;
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
if ( p < e )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Additional data detected after value string and before line end! (0x%lX, 0x%lX, 0x%lX, %s). " , n , read , ( size_t ) ( p - * line ) , k ) ;
2022-04-18 22:38:18 +01:00
goto end ;
}
}
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
/* Empty value string and value string length not being a multiple of 2 are both errors. */
if ( * v = = ' \0 ' | | ( ( l - v ) % 2 ) ! = 0 )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid value string length! (0x%lX, 0x%lX, 0x%lX, %s). " , n , read , ( size_t ) ( l - v ) , k ) ;
2022-04-18 22:38:18 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
/* Update pointers. */
2020-04-11 06:28:26 +01:00
* key = k ;
* value = v ;
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
/* Update return value. */
ret = 0 ;
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
end :
if ( ret ! = 0 )
{
if ( * line ) free ( * line ) ;
* line = * key = * value = NULL ;
}
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
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 ;
2022-07-05 02:04:28 +01:00
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
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2020-07-03 10:31:22 +01:00
if ( value_len ! = hex_str_len )
2020-04-11 06:28:26 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Key \" %s \" must be %u hex digits long! " , key , hex_str_len ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2020-04-11 06:28:26 +01:00
memset ( out , 0 , size ) ;
2022-07-05 02:04:28 +01:00
2020-04-11 06:28:26 +01:00
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 ' )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid hex character in key \" %s \" at position %u! " , key , i ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2020-04-11 06:28:26 +01:00
if ( ( i & 1 ) = = 0 ) val < < = 4 ;
out [ i > > 1 ] | = val ;
}
2022-07-05 02:04:28 +01:00
2020-04-11 06:28:26 +01:00
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 } ;
2023-04-08 12:34:53 +01:00
2022-02-10 18:05:07 +00:00
const char * keys_file_path = ( utilsIsDevelopmentUnit ( ) ? DEV_KEYS_FILE_PATH : PROD_KEYS_FILE_PATH ) ;
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
bool is_mariko = utilsIsMarikoUnit ( ) ;
bool tsec_root_key_available = false , mariko_kek_available = false ;
bool use_personalized_eticket_rsa_kek = ( g_eTicketRsaDeviceKey . generation > 0 ) , eticket_rsa_kek_available = false ;
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-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unable to open \" %s \" to retrieve keys! " , keys_file_path ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
# define PARSE_HEX_KEY(name, out, decl) \
Preliminar 15.x support.
This commit uses my yet unmerged libnx PR to update ncm_types.h.
PoC code hasn't been updated yet, so proper support for DLC updates will arrive at a later time.
Note to self: implement a way to provide access to loaded DataPatch TitleInfo entries (linked list hell).
* bktr: renamed bktrBucketInitializeSubStorageReadParams to bktrInitializeSubStorageReadParams to avoid redundancy, added debug code to dump BucketInfo and BucketTree tables if BucketTree storage initialization fails.
* cnmt: updated ContentMetaAddOnContentMetaExtendedHeader struct to its 15.x equivalent, added ContentMetaLegacyAddOnContentMetaExtendedHeader struct, added ContentMetaDataPatchMetaExtendedHeader struct, updated the cnmtGetRequiredTitleId and cnmtGetRequiredTitleVersion functions to support DataPatch titles, updated cnmtInitializeContext to support both the new AddOnContent extended header and DataPatch titles, added debug code to dump the whole CNMT if context initialization fails, updated cnmtGenerateAuthoringToolXml to support DataPatch titles.
* keys: updated block hashes to match 15.x keyset, use case-insensitive comparison while looking for entry names in keysReadKeysFromFile, make sure the eticket_rsa_kek is non-zero before proceeding in keysGetDecryptedEticketRsaDeviceKey.
* nca: updated NcaKeyGeneration enum, added reminder about updating NcaSignatureKeyGeneration if necessary, replaced ncaFsSectionCheckHashRegionAccess with ncaFsSectionCheckPlaintextHashRegionAccess, removed all extents checks on Patch RomFS and sparse sections, updated ncaGetFsSectionTypeName to reflect if a FS section holds a sparse layer or not.
* nca_storage: updated ncaStorageInitializeContext to avoid initializing a compressed storage if a sparse layer is also used (fixes issues with Them's Fightin' Herds), updated ncaStorageSetPatchOriginalSubStorage to enforce the presence of a compressed storage in a patch if the base FS holds a compressed storage.
* npdm: added reminder about updating NpdmSignatureKeyGeneration if necessary, updated NpdmFsAccessControlFlags enum, updated NpdmAccessibility enum, updated NpdmSystemCallId enum, fixed typos.
* title: updated all relevant functions that deal with NcmContentMetaType values to also handle DataPatch titles, added functions to handle DataPatchId values, removed titleConvertNcmContentSizeToU64 and titleConvertU64ToNcmContentSize functions in favor of ncmContentInfoSizeToU64 and ncmU64ToContentInfoSize from my unmerged libnx PR, updated internal arrays to match 15.x changes, renamed titleOrphanTitleInfoSortFunction to titleInfoEntrySortFunction and updated it to also sort entries by version and storage ID, updated titleGenerateTitleInfoEntriesForTitleStorage to sort TitleInfo entries, simplified titleDuplicateTitleInfo a bit by using macros.
2022-10-23 15:44:47 +01:00
if ( ! strcasecmp ( key , name ) & & keysParseHexKey ( out , key , value , sizeof ( out ) ) ) { \
2022-04-18 22:38:18 +01:00
key_count + + ; \
decl ; \
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
# define PARSE_HEX_KEY_WITH_INDEX(name, idx, out, decl) \
snprintf ( test_name , sizeof ( test_name ) , " %s_%02x " , name , idx ) ; \
PARSE_HEX_KEY ( test_name , out , decl ) ;
2022-07-05 02:04:28 +01:00
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 ;
2022-07-05 02:04:28 +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-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
if ( is_mariko )
2020-04-11 06:28:26 +01:00
{
2023-04-08 12:34:53 +01:00
/* Parse Mariko KEK. */
/* This will only appear on Mariko units. */
if ( ! mariko_kek_available )
{
PARSE_HEX_KEY ( " mariko_kek " , g_nxKeyset . mariko_kek , mariko_kek_available = true ; continue ) ;
}
} else {
/* Parse TSEC root key. */
/* This will only appear on Erista units. */
if ( ! tsec_root_key_available )
{
PARSE_HEX_KEY_WITH_INDEX ( " tsec_root_key " , TSEC_ROOT_KEY_VERSION , g_nxKeyset . tsec_root_key , tsec_root_key_available = true ; continue ) ;
}
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Parse eTicket RSA device KEK. */
/* The personalized entry only appears on consoles that use the new PRODINFO key generation scheme. */
if ( ! eticket_rsa_kek_available )
{
PARSE_HEX_KEY ( use_personalized_eticket_rsa_kek ? " eticket_rsa_kek_personalized " : " eticket_rsa_kek " , g_nxKeyset . eticket_rsa_kek , eticket_rsa_kek_available = true ; continue ) ;
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Parse master keys, starting with the last known one. */
for ( u8 i = NcaKeyGeneration_Current ; i < = NcaKeyGeneration_Max ; i + + )
{
u8 key_gen_val = ( i - 1 ) ;
PARSE_HEX_KEY_WITH_INDEX ( " master_key " , key_gen_val , g_nxKeyset . master_keys [ key_gen_val ] , break ) ;
2020-04-11 06:28:26 +01:00
}
}
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
# undef PARSE_HEX_KEY_WITH_INDEX
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
# undef PARSE_HEX_KEY
2022-07-05 02:04:28 +01:00
2022-04-18 22:38:18 +01:00
if ( line ) free ( line ) ;
2022-07-05 02:04:28 +01:00
2020-04-11 06:28:26 +01:00
fclose ( keys_file ) ;
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Bail out if we didn't retrieve a single key. */
2022-04-18 22:38:18 +01:00
if ( key_count )
2020-04-11 06:28:26 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_INFO ( " Loaded %u key(s) from \" %s \" . " , key_count , keys_file_path ) ;
2022-04-18 22:38:18 +01:00
} else {
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unable to parse keys from \" %s \" ! (keys file empty?). " , keys_file_path ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Check if the latest master key was retrieved. */
g_latestMasterKeyAvailable = ! keysIsKeyEmpty ( g_nxKeyset . master_keys [ NcaKeyGeneration_Current - 1 ] ) ;
if ( ! g_latestMasterKeyAvailable )
{
LOG_MSG_WARNING ( " Latest known master key (%02X) unavailable in \" %s \" . Latest master key derivation will be carried out. " , NcaKeyGeneration_Current - 1 , keys_file_path ) ;
/* Make sure we have what we need to derive the latest master key. */
if ( is_mariko )
{
if ( ! mariko_kek_available )
{
LOG_MSG_ERROR ( " Mariko KEK unavailable in \" %s \" ! " , keys_file_path ) ;
return false ;
}
} else {
if ( ! tsec_root_key_available )
{
LOG_MSG_ERROR ( " TSEC root key unavailable in \" %s \" ! " , keys_file_path ) ;
return false ;
}
}
}
2020-10-21 05:27:48 +01:00
if ( ! eticket_rsa_kek_available )
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " eTicket RSA KEK unavailable in \" %s \" ! " , keys_file_path ) ;
2020-10-21 05:27:48 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2020-04-11 06:28:26 +01:00
return true ;
}
2021-05-11 07:00:33 +01:00
2023-04-08 12:34:53 +01:00
static bool keysDeriveMasterKeys ( void )
2022-09-13 01:22:15 +01:00
{
2023-04-08 12:34:53 +01:00
u8 tmp [ AES_128_KEY_SIZE ] = { 0 } ;
u8 latest_mkey_index = ( NcaKeyGeneration_Current - 1 ) ;
bool is_dev = utilsIsDevelopmentUnit ( ) ;
2022-09-13 01:22:15 +01:00
2023-04-08 12:34:53 +01:00
/* Only derive the latest master key if it hasn't been populated already. */
if ( ! g_latestMasterKeyAvailable )
2022-09-13 01:22:15 +01:00
{
2023-04-08 12:34:53 +01:00
if ( utilsIsMarikoUnit ( ) )
2022-09-13 01:22:15 +01:00
{
2023-04-08 12:34:53 +01:00
/* Derive the latest master KEK using the hardcoded Mariko master KEK source and the Mariko KEK. */
aes128EcbCrypt ( tmp , is_dev ? g_marikoMasterKekSourceDev : g_marikoMasterKekSourceProd , g_nxKeyset . mariko_kek , false ) ;
} else {
/* Derive the latest master KEK using the hardcoded Erista master KEK source and the TSEC root key. */
aes128EcbCrypt ( tmp , g_eristaMasterKekSource , g_nxKeyset . tsec_root_key , false ) ;
2022-09-13 01:22:15 +01:00
}
2023-04-08 12:34:53 +01:00
/* Derive the latest master key using the hardcoded master key source and the latest master KEK. */
aes128EcbCrypt ( g_nxKeyset . master_keys [ latest_mkey_index ] , g_masterKeySource , tmp , false ) ;
2022-09-13 01:22:15 +01:00
}
2023-04-08 12:34:53 +01:00
/* Derive all lower master keys using the latest master key and the master key vectors. */
for ( u8 i = latest_mkey_index ; i > NcaKeyGeneration_Since100NUP ; i - - ) aes128EcbCrypt ( g_nxKeyset . master_keys [ i - 1 ] , is_dev ? g_masterKeyVectorsDev [ i ] : g_masterKeyVectorsProd [ i ] , \
g_nxKeyset . master_keys [ i ] , false ) ;
/* Check if we derived the right keys. */
aes128EcbCrypt ( tmp , is_dev ? g_masterKeyVectorsDev [ NcaKeyGeneration_Since100NUP ] : g_masterKeyVectorsProd [ NcaKeyGeneration_Since100NUP ] , \
g_nxKeyset . master_keys [ NcaKeyGeneration_Since100NUP ] , false ) ;
return keysIsKeyEmpty ( tmp ) ;
2022-09-13 01:22:15 +01:00
}
2023-04-08 12:34:53 +01:00
static bool keysDeriveNcaHeaderKey ( void )
2021-05-11 07:00:33 +01:00
{
2023-04-08 12:34:53 +01:00
u8 nca_header_kek [ AES_128_KEY_SIZE ] = { 0 } ;
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
SmcGenerateAesKekOption option = { 0 } ;
smcPrepareGenerateAesKekOption ( false , SmcKeyType_Default , SmcSealKey_LoadAesKey , & option ) ;
/* Derive nca_header_kek using g_ncaHeaderKekSource and master key 00. */
if ( ! keysGenerateAesKek ( g_ncaHeaderKekSource , NcaKeyGeneration_Since100NUP , option , nca_header_kek ) )
2021-05-11 07:00:33 +01:00
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " Failed to derive NCA header KEK! " ) ;
2021-05-11 07:00:33 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Derive nca_header_key (first half) from nca_header_kek and g_ncaHeaderKeySource. */
if ( ! keysGenerateAesKey ( nca_header_kek , g_ncaHeaderKeySource , g_nxKeyset . nca_header_key ) )
{
LOG_MSG_ERROR ( " Failed to derive NCA header key! (#1). " ) ;
return false ;
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Derive nca_header_key (second half) from nca_header_kek and g_ncaHeaderKeySource. */
if ( ! keysGenerateAesKey ( nca_header_kek , g_ncaHeaderKeySource + AES_128_KEY_SIZE , g_nxKeyset . nca_header_key + AES_128_KEY_SIZE ) )
Preliminar 15.x support.
This commit uses my yet unmerged libnx PR to update ncm_types.h.
PoC code hasn't been updated yet, so proper support for DLC updates will arrive at a later time.
Note to self: implement a way to provide access to loaded DataPatch TitleInfo entries (linked list hell).
* bktr: renamed bktrBucketInitializeSubStorageReadParams to bktrInitializeSubStorageReadParams to avoid redundancy, added debug code to dump BucketInfo and BucketTree tables if BucketTree storage initialization fails.
* cnmt: updated ContentMetaAddOnContentMetaExtendedHeader struct to its 15.x equivalent, added ContentMetaLegacyAddOnContentMetaExtendedHeader struct, added ContentMetaDataPatchMetaExtendedHeader struct, updated the cnmtGetRequiredTitleId and cnmtGetRequiredTitleVersion functions to support DataPatch titles, updated cnmtInitializeContext to support both the new AddOnContent extended header and DataPatch titles, added debug code to dump the whole CNMT if context initialization fails, updated cnmtGenerateAuthoringToolXml to support DataPatch titles.
* keys: updated block hashes to match 15.x keyset, use case-insensitive comparison while looking for entry names in keysReadKeysFromFile, make sure the eticket_rsa_kek is non-zero before proceeding in keysGetDecryptedEticketRsaDeviceKey.
* nca: updated NcaKeyGeneration enum, added reminder about updating NcaSignatureKeyGeneration if necessary, replaced ncaFsSectionCheckHashRegionAccess with ncaFsSectionCheckPlaintextHashRegionAccess, removed all extents checks on Patch RomFS and sparse sections, updated ncaGetFsSectionTypeName to reflect if a FS section holds a sparse layer or not.
* nca_storage: updated ncaStorageInitializeContext to avoid initializing a compressed storage if a sparse layer is also used (fixes issues with Them's Fightin' Herds), updated ncaStorageSetPatchOriginalSubStorage to enforce the presence of a compressed storage in a patch if the base FS holds a compressed storage.
* npdm: added reminder about updating NpdmSignatureKeyGeneration if necessary, updated NpdmFsAccessControlFlags enum, updated NpdmAccessibility enum, updated NpdmSystemCallId enum, fixed typos.
* title: updated all relevant functions that deal with NcmContentMetaType values to also handle DataPatch titles, added functions to handle DataPatchId values, removed titleConvertNcmContentSizeToU64 and titleConvertU64ToNcmContentSize functions in favor of ncmContentInfoSizeToU64 and ncmU64ToContentInfoSize from my unmerged libnx PR, updated internal arrays to match 15.x changes, renamed titleOrphanTitleInfoSortFunction to titleInfoEntrySortFunction and updated it to also sort entries by version and storage ID, updated titleGenerateTitleInfoEntriesForTitleStorage to sort TitleInfo entries, simplified titleDuplicateTitleInfo a bit by using macros.
2022-10-23 15:44:47 +01:00
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " Failed to derive NCA header key! (#2). " ) ;
Preliminar 15.x support.
This commit uses my yet unmerged libnx PR to update ncm_types.h.
PoC code hasn't been updated yet, so proper support for DLC updates will arrive at a later time.
Note to self: implement a way to provide access to loaded DataPatch TitleInfo entries (linked list hell).
* bktr: renamed bktrBucketInitializeSubStorageReadParams to bktrInitializeSubStorageReadParams to avoid redundancy, added debug code to dump BucketInfo and BucketTree tables if BucketTree storage initialization fails.
* cnmt: updated ContentMetaAddOnContentMetaExtendedHeader struct to its 15.x equivalent, added ContentMetaLegacyAddOnContentMetaExtendedHeader struct, added ContentMetaDataPatchMetaExtendedHeader struct, updated the cnmtGetRequiredTitleId and cnmtGetRequiredTitleVersion functions to support DataPatch titles, updated cnmtInitializeContext to support both the new AddOnContent extended header and DataPatch titles, added debug code to dump the whole CNMT if context initialization fails, updated cnmtGenerateAuthoringToolXml to support DataPatch titles.
* keys: updated block hashes to match 15.x keyset, use case-insensitive comparison while looking for entry names in keysReadKeysFromFile, make sure the eticket_rsa_kek is non-zero before proceeding in keysGetDecryptedEticketRsaDeviceKey.
* nca: updated NcaKeyGeneration enum, added reminder about updating NcaSignatureKeyGeneration if necessary, replaced ncaFsSectionCheckHashRegionAccess with ncaFsSectionCheckPlaintextHashRegionAccess, removed all extents checks on Patch RomFS and sparse sections, updated ncaGetFsSectionTypeName to reflect if a FS section holds a sparse layer or not.
* nca_storage: updated ncaStorageInitializeContext to avoid initializing a compressed storage if a sparse layer is also used (fixes issues with Them's Fightin' Herds), updated ncaStorageSetPatchOriginalSubStorage to enforce the presence of a compressed storage in a patch if the base FS holds a compressed storage.
* npdm: added reminder about updating NpdmSignatureKeyGeneration if necessary, updated NpdmFsAccessControlFlags enum, updated NpdmAccessibility enum, updated NpdmSystemCallId enum, fixed typos.
* title: updated all relevant functions that deal with NcmContentMetaType values to also handle DataPatch titles, added functions to handle DataPatchId values, removed titleConvertNcmContentSizeToU64 and titleConvertU64ToNcmContentSize functions in favor of ncmContentInfoSizeToU64 and ncmU64ToContentInfoSize from my unmerged libnx PR, updated internal arrays to match 15.x changes, renamed titleOrphanTitleInfoSortFunction to titleInfoEntrySortFunction and updated it to also sort entries by version and storage ID, updated titleGenerateTitleInfoEntriesForTitleStorage to sort TitleInfo entries, simplified titleDuplicateTitleInfo a bit by using macros.
2022-10-23 15:44:47 +01:00
return false ;
}
2023-04-08 12:34:53 +01:00
return true ;
}
static bool keysDerivePerGenerationKeys ( void )
{
SmcGenerateAesKekOption option = { 0 } ;
smcPrepareGenerateAesKekOption ( false , SmcKeyType_Default , SmcSealKey_LoadAesKey , & option ) ;
bool success = true ;
for ( u8 i = 1 ; i < = NcaKeyGeneration_Max ; i + + )
{
u8 key_gen_val = ( i - 1 ) ;
/* Make sure we're not dealing with an unpopulated master key entry. */
if ( i > NcaKeyGeneration_Current & & keysIsKeyEmpty ( g_nxKeyset . master_keys [ key_gen_val ] ) )
{
//LOG_MSG_DEBUG("Master key %02X unavailable.", key_gen_val);
continue ;
}
/* Derive NCA key area keys for this generation. */
for ( u8 j = 0 ; j < NcaKeyAreaEncryptionKeyIndex_Count ; j + + )
{
if ( ! keysLoadAesKeyFromAesKek ( g_ncaKeyAreaEncryptionKeySources [ j ] , i , option , g_aesKeyGenerationSource , g_nxKeyset . nca_kaek [ j ] [ key_gen_val ] ) )
{
LOG_MSG_DEBUG ( " Failed to derive NCA KAEK for type %u and generation %u! " , j , key_gen_val ) ;
success = false ;
break ;
}
}
if ( ! success ) break ;
/* Derive ticket common key for this generation. */
aes128EcbCrypt ( g_nxKeyset . ticket_common_keys [ key_gen_val ] , g_ticketCommonKeySource , g_nxKeyset . master_keys [ key_gen_val ] , false ) ;
}
return success ;
}
static bool keysDeriveGcCardInfoKey ( void )
{
SmcGenerateAesKekOption option = { 0 } ;
const u8 * key_src = ( utilsIsDevelopmentUnit ( ) ? g_gcCardInfoKeySourceDev : g_gcCardInfoKeySourceProd ) ;
smcPrepareGenerateAesKekOption ( false , SmcKeyType_Default , SmcSealKey_LoadAesKey , & option ) ;
return keysGenerateAesKeyFromAesKek ( g_gcCardInfoKekSource , NcaKeyGeneration_Since100NUP , option , key_src , g_nxKeyset . gc_cardinfo_key ) ;
}
static bool keysGetDecryptedEticketRsaDeviceKey ( void )
{
u32 public_exponent = 0 ;
Aes128CtrContext eticket_aes_ctx = { 0 } ;
EticketRsaDeviceKey * eticket_rsa_key = ( EticketRsaDeviceKey * ) g_eTicketRsaDeviceKey . key ;
2021-05-11 07:00:33 +01:00
/* Decrypt eTicket RSA device key. */
2023-04-08 12:34:53 +01:00
aes128CtrContextCreate ( & eticket_aes_ctx , g_nxKeyset . 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 ) ) ;
2022-07-05 02:04:28 +01:00
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 )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid public exponent for decrypted eTicket RSA device key! Wrong keys? (0x%X). " , public_exponent ) ;
2021-05-11 07:00:33 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2021-05-11 07:00:33 +01:00
/* 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
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " eTicket RSA device key test failed! Wrong keys? " ) ;
2021-05-11 07:00:33 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2021-05-11 07:00:33 +01:00
return true ;
}
static bool keysTestEticketRsaDeviceKey ( const void * e , const void * d , const void * n )
{
if ( ! e | | ! d | | ! n )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2021-05-11 07:00:33 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2021-05-11 07:00:33 +01:00
Result rc = 0 ;
2021-05-21 14:34:43 +01:00
u8 x [ RSA2048_BYTES ] = { 0 } , y [ RSA2048_BYTES ] = { 0 } , z [ RSA2048_BYTES ] = { 0 } ;
2022-07-05 02:04:28 +01:00
2021-05-11 07:00:33 +01:00
/* 0xCAFEBABE. */
x [ 0xFC ] = 0xCA ;
x [ 0xFD ] = 0xFE ;
x [ 0xFE ] = 0xBA ;
x [ 0xFF ] = 0xBE ;
2022-07-05 02:04:28 +01:00
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 ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " splUserExpMod failed! (#1) (0x%X). " , rc ) ;
2021-05-11 07:00:33 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2021-05-11 07:00:33 +01:00
rc = splUserExpMod ( y , n , e , 4 , z ) ;
if ( R_FAILED ( rc ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " splUserExpMod failed! (#2) (0x%X). " , rc ) ;
2021-05-11 07:00:33 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2021-05-21 14:34:43 +01:00
if ( memcmp ( x , z , RSA2048_BYTES ) ! = 0 )
2021-05-11 07:00:33 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid RSA key pair! " ) ;
2021-05-11 07:00:33 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2021-05-11 07:00:33 +01:00
return true ;
}
2021-05-22 09:45:40 +01:00
2023-04-08 12:34:53 +01:00
/* Based on splCryptoGenerateAesKek(). Excludes key sealing and device-unique shenanigans. */
static bool keysGenerateAesKek ( const u8 * kek_src , u8 key_generation , SmcGenerateAesKekOption option , u8 * out_kek )
2021-05-22 09:45:40 +01:00
{
2023-04-08 12:34:53 +01:00
bool is_device_unique = ( option . fields . is_device_unique = = 1 ) ;
u8 key_type_idx = option . fields . key_type_idx ;
u8 seal_key_idx = option . fields . seal_key_idx ;
if ( ! kek_src | | key_generation > NcaKeyGeneration_Max | | is_device_unique | | key_type_idx > = SmcKeyType_Count | | seal_key_idx > = SmcSealKey_Count | | \
option . fields . reserved ! = 0 | | ! out_kek )
2023-03-29 22:14:21 +01:00
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2023-03-29 22:14:21 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
if ( key_generation ) key_generation - - ;
u8 kekek_src [ AES_128_KEY_SIZE ] = { 0 } , kekek [ AES_128_KEY_SIZE ] = { 0 } ;
const u8 * mkey = g_nxKeyset . master_keys [ key_generation ] ;
/* Make sure this master key is available. */
if ( keysIsKeyEmpty ( mkey ) )
2021-05-22 09:45:40 +01:00
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " Master key %02X unavailable! " , key_generation ) ;
2021-05-22 09:45:40 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Derive the KEKEK source using hardcoded data. */
for ( u8 i = 0 ; i < AES_128_KEY_SIZE ; i + + ) kekek_src [ i ] = ( g_smcKeyTypeSources [ key_type_idx ] [ i ] ^ g_smcSealKeyMasks [ seal_key_idx ] [ i ] ) ;
/* Derive the KEKEK using the KEKEK source and the master key. */
aes128EcbCrypt ( kekek , kekek_src , mkey , false ) ;
/* Derive the KEK using the provided KEK source and the derived KEKEK. */
aes128EcbCrypt ( out_kek , kek_src , kekek , false ) ;
2023-03-29 22:14:21 +01:00
return true ;
}
2023-04-08 12:34:53 +01:00
/* Based on splCryptoLoadAesKey(). Excludes key sealing shenanigans. */
static bool keysLoadAesKey ( const u8 * kek , const u8 * key_src , u8 * out_key )
2023-03-29 22:14:21 +01:00
{
2023-04-08 12:34:53 +01:00
if ( ! kek | | ! key_src | | ! out_key )
2023-03-29 22:14:21 +01:00
{
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
return false ;
}
2023-04-08 12:34:53 +01:00
aes128EcbCrypt ( out_key , key_src , kek , false ) ;
2023-03-29 22:14:21 +01:00
2023-04-08 12:34:53 +01:00
return true ;
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
/* Based on splCryptoGenerateAesKey(). Excludes key sealing shenanigans. */
static bool keysGenerateAesKey ( const u8 * kek , const u8 * key_src , u8 * out_key )
{
if ( ! kek | | ! key_src | | ! out_key )
2021-05-22 09:45:40 +01:00
{
2023-04-08 12:34:53 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2021-05-22 09:45:40 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2023-04-08 12:34:53 +01:00
u8 aes_key [ AES_128_KEY_SIZE ] = { 0 } ;
keysLoadAesKey ( kek , g_aesKeyGenerationSource , aes_key ) ;
aes128EcbCrypt ( out_key , key_src , aes_key , false ) ;
2021-05-22 09:45:40 +01:00
return true ;
}
2023-04-08 12:34:53 +01:00
/* Wrapper for keysGenerateAesKek() + keysLoadAesKey() to generate a single usable AES key in one shot. */
static bool keysLoadAesKeyFromAesKek ( const u8 * kek_src , u8 key_generation , SmcGenerateAesKekOption option , const u8 * key_src , u8 * out_key )
{
u8 kek [ AES_128_KEY_SIZE ] = { 0 } ;
return ( keysGenerateAesKek ( kek_src , key_generation , option , kek ) & & keysLoadAesKey ( kek , key_src , out_key ) ) ;
}
/* Wrapper for keysGenerateAesKek() + keysGenerateAesKey() to generate a single usable AES key in one shot. */
static bool keysGenerateAesKeyFromAesKek ( const u8 * kek_src , u8 key_generation , SmcGenerateAesKekOption option , const u8 * key_src , u8 * out_key )
{
u8 kek [ AES_128_KEY_SIZE ] = { 0 } ;
return ( keysGenerateAesKek ( kek_src , key_generation , option , kek ) & & keysGenerateAesKey ( kek , key_src , out_key ) ) ;
}