2020-04-16 01:06:41 +01:00
/*
* Copyright ( c ) 2020 DarkMatterCore
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
2020-04-11 06:28:26 +01:00
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include "tik.h"
# include "save.h"
# include "es.h"
# include "keys.h"
# include "rsa.h"
2020-04-17 22:59:05 +01:00
# include "gamecard.h"
2020-04-11 06:28:26 +01:00
# include "utils.h"
2020-04-17 22:59:05 +01:00
# define TIK_COMMON_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME " / save / 80000000000000e1"
# define TIK_PERSONALIZED_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME " / save / 80000000000000e2"
2020-04-11 06:28:26 +01:00
# define TIK_SAVEFILE_STORAGE_PATH " / ticket.bin"
# define ETICKET_DEVKEY_PUBLIC_EXPONENT 0x10001
2020-04-15 21:50:07 +01:00
/* Type definitions. */
2020-04-11 06:28:26 +01:00
/// Everything after the AES CTR is encrypted.
typedef struct {
u8 ctr [ 0x10 ] ;
u8 exponent [ 0x100 ] ;
u8 modulus [ 0x100 ] ;
u32 public_exponent ; ///< Must match ETICKET_DEVKEY_PUBLIC_EXPONENT. Stored using big endian byte order.
u8 padding [ 0x14 ] ;
2020-04-15 06:59:12 +01:00
u64 device_id ;
2020-04-11 06:28:26 +01:00
u8 ghash [ 0x10 ] ;
} tikEticketDeviceKeyData ;
2020-04-15 21:50:07 +01:00
/* Global variables. */
2020-04-11 06:28:26 +01:00
static SetCalRsa2048DeviceKey g_eTicketDeviceKey = { 0 } ;
static bool g_eTicketDeviceKeyRetrieved = false ;
2020-04-15 21:50:07 +01:00
/// Used during the RSA-OAEP titlekey decryption stage.
2020-04-11 06:28:26 +01:00
static const u8 g_nullHash [ 0x20 ] = {
0xE3 , 0xB0 , 0xC4 , 0x42 , 0x98 , 0xFC , 0x1C , 0x14 , 0x9A , 0xFB , 0xF4 , 0xC8 , 0x99 , 0x6F , 0xB9 , 0x24 ,
0x27 , 0xAE , 0x41 , 0xE4 , 0x64 , 0x9B , 0x93 , 0x4C , 0xA4 , 0x95 , 0x99 , 0x1B , 0x78 , 0x52 , 0xB8 , 0x55
} ;
2020-04-15 21:50:07 +01:00
/* Function prototypes. */
2020-04-17 22:59:05 +01:00
static bool tikRetrieveTicketFromGameCardByRightsId ( Ticket * dst , const FsRightsId * id ) ;
static bool tikRetrieveTicketFromEsSaveDataByRightsId ( Ticket * dst , const FsRightsId * id ) ;
static TikCommonBlock * tikGetCommonBlockFromMemoryBuffer ( void * data ) ;
2020-04-20 11:39:41 +01:00
static bool tikGetTitleKekEncryptedTitleKeyFromTicket ( Ticket * tik ) ;
2020-04-17 22:59:05 +01:00
static bool tikGetTitleKekDecryptedTitleKey ( void * dst , const void * src , u8 key_generation ) ;
2020-04-19 23:44:22 +01:00
static bool tikGetTitleKeyTypeFromRightsId ( const FsRightsId * id , u8 * out ) ;
2020-04-17 22:59:05 +01:00
static bool tikRetrieveRightsIdsByTitleKeyType ( FsRightsId * * out , u32 * out_count , bool personalized ) ;
2020-04-11 06:28:26 +01:00
static bool tikGetTicketTypeAndSize ( const void * data , u64 data_size , u8 * out_type , u64 * out_size ) ;
2020-04-17 22:59:05 +01:00
2020-04-11 06:28:26 +01:00
static bool tikRetrieveEticketDeviceKey ( void ) ;
2020-04-17 22:59:05 +01:00
static bool tikTestKeyPairFromEticketDeviceKey ( const void * e , const void * d , const void * n ) ;
2020-04-11 06:28:26 +01:00
2020-04-17 22:59:05 +01:00
bool tikRetrieveTicketByRightsId ( Ticket * dst , const FsRightsId * id , bool use_gamecard )
2020-04-11 06:28:26 +01:00
{
2020-04-20 11:39:41 +01:00
if ( ! dst | | ! id )
2020-04-17 22:59:05 +01:00
{
2020-04-20 11:39:41 +01:00
LOGFILE ( " Invalid parameters! " ) ;
2020-04-17 22:59:05 +01:00
return false ;
}
2020-04-20 11:39:41 +01:00
/* Check if this ticket has already been retrieved */
2020-04-29 22:11:27 +01:00
if ( dst - > type > TikType_None & & dst - > type < = TikType_SigHmac160 & & dst - > size > = TIK_MIN_SIZE & & dst - > size < = TIK_MAX_SIZE )
2020-04-17 22:59:05 +01:00
{
2020-04-20 11:39:41 +01:00
TikCommonBlock * tik_common_blk = tikGetCommonBlockFromTicket ( dst ) ;
if ( tik_common_blk & & ! memcmp ( tik_common_blk - > rights_id . c , id - > c , 0x10 ) ) return true ;
}
bool tik_retrieved = ( use_gamecard ? tikRetrieveTicketFromGameCardByRightsId ( dst , id ) : tikRetrieveTicketFromEsSaveDataByRightsId ( dst , id ) ) ;
if ( ! tik_retrieved )
{
LOGFILE ( " Unable to retrieve ticket data! " ) ;
2020-04-17 22:59:05 +01:00
return false ;
}
2020-04-20 11:39:41 +01:00
if ( ! tikGetTitleKekEncryptedTitleKeyFromTicket ( dst ) )
2020-04-17 22:59:05 +01:00
{
LOGFILE ( " Unable to retrieve titlekey from ticket! " ) ;
return false ;
}
/* Even though tickets do have a proper key_generation field, we'll just retrieve it from the rights_id field */
/* Old custom tools used to wipe the key_generation field or save it to a different offset */
2020-04-20 11:39:41 +01:00
if ( ! tikGetTitleKekDecryptedTitleKey ( dst - > dec_titlekey , dst - > enc_titlekey , id - > c [ 0xF ] ) )
2020-04-17 22:59:05 +01:00
{
LOGFILE ( " Unable to perform titlekek decryption! " ) ;
return false ;
}
return true ;
}
TikCommonBlock * tikGetCommonBlockFromTicket ( Ticket * tik )
{
2020-04-29 22:11:27 +01:00
if ( ! tik | | tik - > type = = TikType_None | | tik - > type > TikType_SigHmac160 | | tik - > size < TIK_MIN_SIZE | | tik - > size > TIK_MAX_SIZE )
2020-04-11 06:28:26 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
return NULL ;
}
2020-04-19 23:44:22 +01:00
TikCommonBlock * tik_common_blk = NULL ;
switch ( tik - > type )
{
case TikType_SigRsa4096 :
tik_common_blk = & ( ( ( TikSigRsa4096 * ) tik - > data ) - > tik_common_blk ) ;
break ;
case TikType_SigRsa2048 :
tik_common_blk = & ( ( ( TikSigRsa2048 * ) tik - > data ) - > tik_common_blk ) ;
break ;
2020-04-29 22:11:27 +01:00
case TikType_SigEcc480 :
tik_common_blk = & ( ( ( TikSigEcc480 * ) tik - > data ) - > tik_common_blk ) ;
break ;
case TikType_SigHmac160 :
tik_common_blk = & ( ( ( TikSigHmac160 * ) tik - > data ) - > tik_common_blk ) ;
2020-04-19 23:44:22 +01:00
break ;
default :
break ;
}
return tik_common_blk ;
2020-04-17 22:59:05 +01:00
}
void tikConvertPersonalizedTicketToCommonTicket ( Ticket * tik )
{
2020-04-29 22:11:27 +01:00
if ( ! tik | | tik - > type = = TikType_None | | tik - > type > TikType_SigHmac160 | | tik - > size < TIK_MIN_SIZE | | tik - > size > TIK_MAX_SIZE ) return ;
2020-04-17 22:59:05 +01:00
bool dev_cert = false ;
2020-04-11 06:28:26 +01:00
TikCommonBlock * tik_common_blk = NULL ;
2020-04-19 23:44:22 +01:00
tik_common_blk = tikGetCommonBlockFromTicket ( tik ) ;
2020-04-17 22:59:05 +01:00
if ( ! tik_common_blk | | tik_common_blk - > titlekey_type ! = TikTitleKeyType_Personalized ) return ;
2020-04-11 06:28:26 +01:00
2020-04-17 22:59:05 +01:00
switch ( tik - > type )
2020-04-11 06:28:26 +01:00
{
2020-04-17 22:59:05 +01:00
case TikType_SigRsa4096 :
tik - > size = sizeof ( TikSigRsa4096 ) ;
memset ( tik - > data + 4 , 0xFF , MEMBER_SIZE ( SignatureBlockRsa4096 , signature ) ) ;
2020-04-11 06:28:26 +01:00
break ;
2020-04-17 22:59:05 +01:00
case TikType_SigRsa2048 :
tik - > size = sizeof ( TikSigRsa2048 ) ;
memset ( tik - > data + 4 , 0xFF , MEMBER_SIZE ( SignatureBlockRsa2048 , signature ) ) ;
2020-04-11 06:28:26 +01:00
break ;
2020-04-29 22:11:27 +01:00
case TikType_SigEcc480 :
tik - > size = sizeof ( TikSigEcc480 ) ;
memset ( tik - > data + 4 , 0xFF , MEMBER_SIZE ( SignatureBlockEcc480 , signature ) ) ;
break ;
case TikType_SigHmac160 :
tik - > size = sizeof ( TikSigHmac160 ) ;
memset ( tik - > data + 4 , 0xFF , MEMBER_SIZE ( SignatureBlockHmac160 , signature ) ) ;
2020-04-11 06:28:26 +01:00
break ;
default :
2020-04-17 22:59:05 +01:00
break ;
2020-04-11 06:28:26 +01:00
}
2020-04-17 22:59:05 +01:00
dev_cert = ( strstr ( tik_common_blk - > issuer , " CA00000004 " ) ! = NULL ) ;
memset ( tik_common_blk - > issuer , 0 , sizeof ( tik_common_blk - > issuer ) ) ;
sprintf ( tik_common_blk - > issuer , " Root-CA%08X-XS00000020 " , dev_cert ? 4 : 3 ) ;
memset ( tik_common_blk - > titlekey_block , 0 , sizeof ( tik_common_blk - > titlekey_block ) ) ;
memcpy ( tik_common_blk - > titlekey_block , tik - > enc_titlekey , 0x10 ) ;
tik_common_blk - > titlekey_type = TikTitleKeyType_Common ;
tik_common_blk - > ticket_id = 0 ;
tik_common_blk - > device_id = 0 ;
tik_common_blk - > account_id = 0 ;
tik_common_blk - > sect_total_size = 0 ;
tik_common_blk - > sect_hdr_offset = ( u32 ) tik - > size ;
tik_common_blk - > sect_hdr_count = 0 ;
tik_common_blk - > sect_hdr_entry_size = 0 ;
memset ( tik - > data + tik - > size , 0 , TIK_MAX_SIZE - tik - > size ) ;
}
static bool tikRetrieveTicketFromGameCardByRightsId ( Ticket * dst , const FsRightsId * id )
{
if ( ! dst | | ! id )
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
char tik_filename [ 0x30 ] = { 0 } ;
u64 tik_offset = 0 , tik_size = 0 ;
utilsGenerateHexStringFromData ( tik_filename , sizeof ( tik_filename ) , id - > c , 0x10 ) ;
strcat ( tik_filename , " .tik " ) ;
2020-04-24 10:38:13 +01:00
if ( ! gamecardGetEntryInfoFromHashFileSystemPartitionByName ( GameCardHashFileSystemPartitionType_Secure , tik_filename , & tik_offset , & tik_size ) )
2020-04-17 22:59:05 +01:00
{
LOGFILE ( " Error retrieving offset and size for \" %s \" entry in secure hash FS partition! " ) ;
return false ;
}
if ( tik_size < TIK_MIN_SIZE | | tik_size > TIK_MAX_SIZE )
{
LOGFILE ( " Invalid size for \" %s \" ! (0x%lX) " , tik_filename , tik_size ) ;
return false ;
}
2020-04-24 10:38:13 +01:00
if ( ! gamecardReadStorage ( dst - > data , tik_size , tik_offset ) )
2020-04-17 22:59:05 +01:00
{
LOGFILE ( " Failed to read \" %s \" data from the inserted gamecard! " , tik_filename ) ;
return false ;
}
if ( ! tikGetTicketTypeAndSize ( dst - > data , tik_size , & ( dst - > type ) , & ( dst - > size ) ) )
{
LOGFILE ( " Unable to determine ticket type and size! " ) ;
return false ;
}
return true ;
2020-04-11 06:28:26 +01:00
}
2020-04-17 22:59:05 +01:00
static bool tikRetrieveTicketFromEsSaveDataByRightsId ( Ticket * dst , const FsRightsId * id )
2020-04-11 06:28:26 +01:00
{
if ( ! dst | | ! id )
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
u32 i ;
2020-04-19 23:44:22 +01:00
u8 titlekey_type = 0 ;
2020-04-11 06:28:26 +01:00
save_ctx_t * save_ctx = NULL ;
allocation_table_storage_ctx_t fat_storage = { 0 } ;
u64 ticket_bin_size = 0 ;
2020-04-19 23:44:22 +01:00
u64 buf_size = ( TIK_MAX_SIZE * 0x10 ) ;
2020-04-17 22:59:05 +01:00
u64 br = 0 , total_br = 0 ;
2020-04-11 06:28:26 +01:00
u8 * ticket_bin_buf = NULL ;
bool found_tik = false , success = false ;
2020-04-19 23:44:22 +01:00
if ( ! tikGetTitleKeyTypeFromRightsId ( id , & titlekey_type ) )
2020-04-11 06:28:26 +01:00
{
LOGFILE ( " Unable to retrieve ticket titlekey type! " ) ;
return false ;
}
2020-04-17 22:59:05 +01:00
save_ctx = save_open_savefile ( titlekey_type = = TikTitleKeyType_Common ? TIK_COMMON_SAVEFILE_PATH : TIK_PERSONALIZED_SAVEFILE_PATH , 0 ) ;
2020-04-11 06:28:26 +01:00
if ( ! save_ctx )
{
2020-04-17 22:59:05 +01:00
LOGFILE ( " Failed to open ES %s ticket system savefile! " , titlekey_type = = TikTitleKeyType_Common ? " common " : " personalized " ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
if ( ! save_get_fat_storage_from_file_entry_by_path ( save_ctx , TIK_SAVEFILE_STORAGE_PATH , & fat_storage , & ticket_bin_size ) )
{
2020-04-17 22:59:05 +01:00
LOGFILE ( " Failed to locate \" %s \" in ES %s ticket system save! " , TIK_SAVEFILE_STORAGE_PATH , titlekey_type = = TikTitleKeyType_Common ? " common " : " personalized " ) ;
2020-04-11 06:28:26 +01:00
goto out ;
}
if ( ticket_bin_size < TIK_MIN_SIZE | | ( ticket_bin_size % TIK_MAX_SIZE ) ! = 0 )
{
LOGFILE ( " Invalid size for \" %s \" ! (0x%lX) " , TIK_SAVEFILE_STORAGE_PATH , ticket_bin_size ) ;
goto out ;
}
ticket_bin_buf = malloc ( buf_size ) ;
2020-04-17 22:59:05 +01:00
if ( ! ticket_bin_buf )
2020-04-11 06:28:26 +01:00
{
2020-04-17 22:59:05 +01:00
LOGFILE ( " Unable to allocate 0x%lX bytes block for temporary read buffer! " , buf_size ) ;
2020-04-11 06:28:26 +01:00
goto out ;
}
2020-04-17 22:59:05 +01:00
while ( total_br < ticket_bin_size )
2020-04-11 06:28:26 +01:00
{
2020-04-17 22:59:05 +01:00
if ( buf_size > ( ticket_bin_size - total_br ) ) buf_size = ( ticket_bin_size - total_br ) ;
2020-04-11 06:28:26 +01:00
br = save_allocation_table_storage_read ( & fat_storage , ticket_bin_buf , total_br , buf_size ) ;
if ( br ! = buf_size )
{
2020-04-17 22:59:05 +01:00
LOGFILE ( " Failed to read 0x%lX bytes chunk at offset 0x%lX from \" %s \" in ES %s ticket system save! " , buf_size , total_br , TIK_SAVEFILE_STORAGE_PATH , \
( titlekey_type = = TikTitleKeyType_Common ? " common " : " personalized " ) ) ;
2020-04-11 06:28:26 +01:00
goto out ;
}
2020-04-17 22:59:05 +01:00
total_br + = br ;
2020-04-11 06:28:26 +01:00
for ( i = 0 ; i < buf_size ; i + = TIK_MAX_SIZE )
{
2020-04-17 22:59:05 +01:00
if ( ( buf_size - i ) < TIK_MIN_SIZE ) break ;
2020-04-11 06:28:26 +01:00
2020-04-17 22:59:05 +01:00
TikCommonBlock * tik_common_blk = tikGetCommonBlockFromMemoryBuffer ( ticket_bin_buf + i ) ;
if ( tik_common_blk & & ! memcmp ( tik_common_blk - > rights_id . c , id - > c , 0x10 ) )
{
/* Jackpot */
found_tik = true ;
break ;
}
2020-04-11 06:28:26 +01:00
}
2020-04-17 22:59:05 +01:00
if ( found_tik ) break ;
2020-04-11 06:28:26 +01:00
}
if ( ! found_tik )
{
LOGFILE ( " Unable to find a matching ticket entry for the provided Rights ID! " ) ;
goto out ;
}
if ( ! tikGetTicketTypeAndSize ( ticket_bin_buf + i , TIK_MAX_SIZE , & ( dst - > type ) , & ( dst - > size ) ) )
{
LOGFILE ( " Unable to determine ticket type and size! " ) ;
goto out ;
}
memcpy ( dst - > data , ticket_bin_buf + i , dst - > size ) ;
success = true ;
out :
if ( ticket_bin_buf ) free ( ticket_bin_buf ) ;
if ( save_ctx ) save_close_savefile ( save_ctx ) ;
return success ;
}
2020-04-17 22:59:05 +01:00
static TikCommonBlock * tikGetCommonBlockFromMemoryBuffer ( void * data )
{
if ( ! data )
{
LOGFILE ( " Invalid parameters! " ) ;
return NULL ;
}
2020-04-19 23:44:22 +01:00
u32 sig_type = 0 ;
2020-04-17 22:59:05 +01:00
u8 * data_u8 = ( u8 * ) data ;
TikCommonBlock * tik_common_blk = NULL ;
memcpy ( & sig_type , data_u8 , sizeof ( u32 ) ) ;
switch ( sig_type )
{
case SignatureType_Rsa4096Sha1 :
case SignatureType_Rsa4096Sha256 :
tik_common_blk = ( TikCommonBlock * ) ( data_u8 + sizeof ( SignatureBlockRsa4096 ) ) ;
break ;
case SignatureType_Rsa2048Sha1 :
case SignatureType_Rsa2048Sha256 :
tik_common_blk = ( TikCommonBlock * ) ( data_u8 + sizeof ( SignatureBlockRsa2048 ) ) ;
break ;
2020-04-29 22:11:27 +01:00
case SignatureType_Ecc480Sha1 :
case SignatureType_Ecc480Sha256 :
tik_common_blk = ( TikCommonBlock * ) ( data_u8 + sizeof ( SignatureBlockEcc480 ) ) ;
break ;
case SignatureType_Hmac160Sha1 :
tik_common_blk = ( TikCommonBlock * ) ( data_u8 + sizeof ( SignatureBlockHmac160 ) ) ;
2020-04-17 22:59:05 +01:00
break ;
default :
LOGFILE ( " Invalid signature type value! (0x%08X) " , sig_type ) ;
return NULL ;
}
return tik_common_blk ;
}
2020-04-20 11:39:41 +01:00
static bool tikGetTitleKekEncryptedTitleKeyFromTicket ( Ticket * tik )
2020-04-11 06:28:26 +01:00
{
2020-04-20 11:39:41 +01:00
if ( ! tik )
2020-04-11 06:28:26 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
size_t out_keydata_size = 0 ;
u8 out_keydata [ 0x100 ] = { 0 } ;
2020-04-20 11:39:41 +01:00
TikCommonBlock * tik_common_blk = NULL ;
2020-04-11 06:28:26 +01:00
tikEticketDeviceKeyData * eticket_devkey = NULL ;
2020-04-20 11:39:41 +01:00
tik_common_blk = tikGetCommonBlockFromTicket ( tik ) ;
if ( ! tik_common_blk )
{
LOGFILE ( " Unable to retrieve common block from ticket! " ) ;
return false ;
}
2020-04-17 22:59:05 +01:00
switch ( tik_common_blk - > titlekey_type )
2020-04-11 06:28:26 +01:00
{
case TikTitleKeyType_Common :
/* No titlekek crypto used */
2020-04-20 11:39:41 +01:00
memcpy ( tik - > enc_titlekey , tik_common_blk - > titlekey_block , 0x10 ) ;
2020-04-11 06:28:26 +01:00
break ;
case TikTitleKeyType_Personalized :
/* Retrieve eTicket device key */
if ( ! tikRetrieveEticketDeviceKey ( ) )
{
LOGFILE ( " Unable to retrieve eTicket device key! " ) ;
return false ;
}
eticket_devkey = ( tikEticketDeviceKeyData * ) g_eTicketDeviceKey . key ;
/* Perform a RSA-OAEP decrypt operation to get the titlekey */
2020-04-17 22:59:05 +01:00
if ( ! rsa2048OaepDecryptAndVerify ( out_keydata , 0x100 , tik_common_blk - > titlekey_block , eticket_devkey - > modulus , eticket_devkey - > exponent , 0x100 , g_nullHash , & out_keydata_size ) | | \
out_keydata_size < 0x10 )
2020-04-11 06:28:26 +01:00
{
LOGFILE ( " RSA-OAEP titlekey decryption failed! " ) ;
return false ;
}
/* Copy decrypted titlekey */
2020-04-20 11:39:41 +01:00
memcpy ( tik - > enc_titlekey , out_keydata , 0x10 ) ;
2020-04-11 06:28:26 +01:00
break ;
default :
2020-04-17 22:59:05 +01:00
LOGFILE ( " Invalid titlekey type value! (0x%02X) " , tik_common_blk - > titlekey_type ) ;
2020-04-11 06:28:26 +01:00
return false ;
}
return true ;
}
2020-04-17 22:59:05 +01:00
static bool tikGetTitleKekDecryptedTitleKey ( void * dst , const void * src , u8 key_generation )
2020-04-11 06:28:26 +01:00
{
if ( ! dst | | ! src )
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
const u8 * titlekek = NULL ;
Aes128Context titlekey_aes_ctx = { 0 } ;
titlekek = keysGetTitlekek ( key_generation ) ;
if ( ! titlekek )
{
LOGFILE ( " Unable to retrieve titlekek for key generation 0x%02X! " , key_generation ) ;
return false ;
}
aes128ContextCreate ( & titlekey_aes_ctx , titlekek , false ) ;
aes128DecryptBlock ( & titlekey_aes_ctx , dst , src ) ;
return true ;
}
2020-04-19 23:44:22 +01:00
static bool tikGetTitleKeyTypeFromRightsId ( const FsRightsId * id , u8 * out )
2020-04-11 06:28:26 +01:00
{
2020-04-19 23:44:22 +01:00
if ( ! id | | ! out )
2020-04-11 06:28:26 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
2020-04-19 23:44:22 +01:00
return false ;
2020-04-11 06:28:26 +01:00
}
2020-04-17 22:59:05 +01:00
u32 count ;
FsRightsId * rights_ids ;
2020-04-19 23:44:22 +01:00
bool found = false ;
2020-04-11 06:28:26 +01:00
2020-04-17 22:59:05 +01:00
for ( u8 i = 0 ; i < 2 ; i + + )
2020-04-11 06:28:26 +01:00
{
2020-04-17 22:59:05 +01:00
count = 0 ;
rights_ids = NULL ;
2020-04-19 23:44:22 +01:00
if ( ! tikRetrieveRightsIdsByTitleKeyType ( & rights_ids , & count , i = = 1 ) )
2020-04-17 22:59:05 +01:00
{
LOGFILE ( " Unable to retrieve %s rights IDs! " , i = = 0 ? " common " : " personalized " ) ;
2020-04-19 23:44:22 +01:00
continue ;
2020-04-17 22:59:05 +01:00
}
if ( ! count ) continue ;
for ( u32 j = 0 ; j < count ; j + + )
{
if ( ! memcmp ( rights_ids [ j ] . c , id - > c , 0x10 ) )
{
2020-04-19 23:44:22 +01:00
* out = i ; /* TikTitleKeyType_Common or TikTitleKeyType_Personalized */
found = true ;
2020-04-17 22:59:05 +01:00
break ;
}
}
free ( rights_ids ) ;
2020-04-19 23:44:22 +01:00
if ( found ) break ;
2020-04-11 06:28:26 +01:00
}
2020-04-19 23:44:22 +01:00
return found ;
2020-04-11 06:28:26 +01:00
}
static bool tikRetrieveRightsIdsByTitleKeyType ( FsRightsId * * out , u32 * out_count , bool personalized )
{
if ( ! out | | ! out_count )
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
Result rc = 0 ;
u32 count = 0 , ids_written = 0 ;
FsRightsId * rights_ids = NULL ;
rc = ( personalized ? esCountPersonalizedTicket ( ( s32 * ) & count ) : esCountCommonTicket ( ( s32 * ) & count ) ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " esCount%sTicket failed! (0x%08X) " , personalized ? " Personalized " : " Common " , rc ) ;
return false ;
}
if ( ! count )
{
LOGFILE ( " No %s tickets available! " , personalized ? " personalized " : " common " ) ;
* out_count = 0 ;
return true ;
}
rights_ids = calloc ( count , sizeof ( FsRightsId ) ) ;
if ( ! rights_ids )
{
LOGFILE ( " Unable to allocate memory for %s rights IDs! " , personalized ? " personalized " : " common " ) ;
return false ;
}
2020-04-17 22:59:05 +01:00
rc = ( personalized ? esListPersonalizedTicket ( ( s32 * ) & ids_written , rights_ids , ( s32 ) count ) : esListCommonTicket ( ( s32 * ) & ids_written , rights_ids , ( s32 ) count ) ) ;
2020-04-11 06:28:26 +01:00
if ( R_FAILED ( rc ) | | ids_written ! = count )
{
LOGFILE ( " esList%sTicket failed! (0x%08X) | Wrote %u entries, expected %u entries " , personalized ? " Personalized " : " Common " , rc , ids_written , count ) ;
free ( rights_ids ) ;
return false ;
}
* out = rights_ids ;
* out_count = count ;
return true ;
}
static bool tikGetTicketTypeAndSize ( const void * data , u64 data_size , u8 * out_type , u64 * out_size )
{
if ( ! data | | data_size < TIK_MIN_SIZE | | data_size > TIK_MAX_SIZE | | ! out_type | | ! out_size )
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
2020-04-17 22:59:05 +01:00
u32 sig_type = 0 ;
2020-04-11 06:28:26 +01:00
u64 offset = 0 ;
2020-04-19 23:44:22 +01:00
u8 type = TikType_None ;
const u8 * data_u8 = ( const u8 * ) data ;
const TikCommonBlock * tik_common_blk = NULL ;
2020-04-11 06:28:26 +01:00
memcpy ( & sig_type , data_u8 , sizeof ( u32 ) ) ;
switch ( sig_type )
{
case SignatureType_Rsa4096Sha1 :
case SignatureType_Rsa4096Sha256 :
type = TikType_SigRsa4096 ;
offset + = sizeof ( SignatureBlockRsa4096 ) ;
break ;
case SignatureType_Rsa2048Sha1 :
case SignatureType_Rsa2048Sha256 :
type = TikType_SigRsa2048 ;
offset + = sizeof ( SignatureBlockRsa2048 ) ;
break ;
2020-04-29 22:11:27 +01:00
case SignatureType_Ecc480Sha1 :
case SignatureType_Ecc480Sha256 :
type = TikType_SigEcc480 ;
offset + = sizeof ( SignatureBlockEcc480 ) ;
break ;
case SignatureType_Hmac160Sha1 :
type = TikType_SigHmac160 ;
offset + = sizeof ( SignatureBlockHmac160 ) ;
2020-04-11 06:28:26 +01:00
break ;
default :
LOGFILE ( " Invalid signature type value! (0x%08X) " , sig_type ) ;
return false ;
}
tik_common_blk = ( const TikCommonBlock * ) ( data_u8 + offset ) ;
offset + = sizeof ( TikCommonBlock ) ;
if ( ( u32 ) offset ! = tik_common_blk - > sect_hdr_offset )
{
LOGFILE ( " Calculated ticket common block end offset doesn't match ESv2 section records header offset! 0x%X != 0x%X " , ( u32 ) offset , tik_common_blk - > sect_hdr_offset ) ;
return false ;
}
for ( u32 i = 0 ; i < tik_common_blk - > sect_hdr_count ; i + + )
{
const TikEsv2SectionRecord * rec = ( const TikEsv2SectionRecord * ) ( data_u8 + offset ) ;
offset + = sizeof ( TikEsv2SectionRecord ) ;
offset + = ( ( u64 ) rec - > record_count * ( u64 ) rec - > record_size ) ;
if ( offset > data_size )
{
LOGFILE ( " Offset calculation exceeded input buffer size while counting ESv2 section records! (0x%lX) " , offset ) ;
return false ;
}
}
* out_type = type ;
* out_size = offset ;
return true ;
}
static bool tikRetrieveEticketDeviceKey ( void )
{
if ( g_eTicketDeviceKeyRetrieved ) return true ;
Result rc = 0 ;
u32 public_exponent = 0 ;
tikEticketDeviceKeyData * eticket_devkey = NULL ;
Aes128CtrContext eticket_aes_ctx = { 0 } ;
rc = setcalGetEticketDeviceKey ( & g_eTicketDeviceKey ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " setcalGetEticketDeviceKey failed! (0x%08X) " , rc ) ;
return false ;
}
/* Decrypt eTicket RSA key */
eticket_devkey = ( tikEticketDeviceKeyData * ) g_eTicketDeviceKey . key ;
aes128CtrContextCreate ( & eticket_aes_ctx , keysGetEticketRsaKek ( ) , eticket_devkey - > ctr ) ;
aes128CtrCrypt ( & eticket_aes_ctx , & ( eticket_devkey - > exponent ) , & ( eticket_devkey - > exponent ) , sizeof ( tikEticketDeviceKeyData ) - 0x10 ) ;
/* Public exponent value must be 0x10001 */
/* It is stored use big endian byte order */
public_exponent = __builtin_bswap32 ( eticket_devkey - > public_exponent ) ;
if ( public_exponent ! = ETICKET_DEVKEY_PUBLIC_EXPONENT )
{
LOGFILE ( " Invalid public RSA exponent for eTicket device key! Wrong keys? (0x%08X) " , public_exponent ) ;
return false ;
}
/* Test RSA key pair */
if ( ! tikTestKeyPairFromEticketDeviceKey ( & ( eticket_devkey - > public_exponent ) , eticket_devkey - > exponent , eticket_devkey - > modulus ) )
{
LOGFILE ( " RSA key pair test failed! Wrong keys? " ) ;
return false ;
}
g_eTicketDeviceKeyRetrieved = true ;
return true ;
}
2020-04-17 22:59:05 +01:00
static bool tikTestKeyPairFromEticketDeviceKey ( const void * e , const void * d , const void * n )
{
if ( ! e | | ! d | | ! n )
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
Result rc = 0 ;
u8 x [ 0x100 ] = { 0 } , y [ 0x100 ] = { 0 } , z [ 0x100 ] = { 0 } ;
/* 0xCAFEBABE */
x [ 0xFC ] = 0xCA ;
x [ 0xFD ] = 0xFE ;
x [ 0xFE ] = 0xBA ;
x [ 0xFF ] = 0xBE ;
rc = splUserExpMod ( x , n , d , 0x100 , y ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " splUserExpMod failed! (#1) (0x%08X) " , rc ) ;
return false ;
}
rc = splUserExpMod ( y , n , e , 4 , z ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " splUserExpMod failed! (#2) (0x%08X) " , rc ) ;
return false ;
}
if ( memcmp ( x , z , 0x100 ) ! = 0 )
{
LOGFILE ( " Invalid RSA key pair! " ) ;
return false ;
}
return true ;
}