More improvements.
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -20,15 +20,15 @@ Steps to reproduce the behavior:
|
|||
**Screenshots**
|
||||
Add screenshots to help explain your problem.
|
||||
|
||||
**Please complete the following information:**
|
||||
- Horizon OS (Switch FW) version: [e.g. 9.0.1]
|
||||
**Please fill the following information:**
|
||||
- Horizon OS (Switch FW) version: [e.g. 10.0.0]
|
||||
- CFW: [e.g. Atmosphère, SX OS, etc.]
|
||||
- CFW version: [e.g. 0.9.4, 2.9.2, etc.]
|
||||
- CFW version: [e.g. 0.11.1, 2.9.4, etc.]
|
||||
- Atmosphère launch method (if applicable): [e.g. Hekate, fusee-primary]
|
||||
- NXDumpTool version: [e.g. 1.1.7]
|
||||
- Homebrew launch method: [e.g. title override, Album applet]
|
||||
- NXDumpTool version: [e.g. 1.2.0]
|
||||
- Homebrew launch method: [e.g. title override, applet]
|
||||
- Source storage used with the application (if applicable): [e.g. gamecard, SD/eMMC]
|
||||
- SD card specs: [e.g. Samsung EVO 256 GB]
|
||||
- SD card specs: [e.g. Samsung EVO 256 GB, FAT32 partition]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
todo:
|
||||
|
||||
hfs0 methods
|
||||
tik gamecard
|
||||
pfs0: full header aligned to 0x20
|
||||
|
||||
|
||||
|
||||
|
|
Before Width: | Height: | Size: 629 B |
Before Width: | Height: | Size: 434 B |
Before Width: | Height: | Size: 637 B |
Before Width: | Height: | Size: 422 B |
Before Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 425 B |
Before Width: | Height: | Size: 630 B |
Before Width: | Height: | Size: 435 B |
|
@ -25,7 +25,8 @@
|
|||
#define CERT_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e0"
|
||||
#define CERT_SAVEFILE_STORAGE_BASE_PATH "/certificate/"
|
||||
|
||||
#define CERT_TYPE(sig) (pub_key_type == CertPubKeyType_Rsa4096 ? CertType_Sig##sig##_PubKeyRsa4096 : (pub_key_type == CertPubKeyType_Rsa2048 ? CertType_Sig##sig##_PubKeyRsa2048 : CertType_Sig##sig##_PubKeyEcsda240))
|
||||
#define CERT_TYPE(sig) (pub_key_type == CertPubKeyType_Rsa4096 ? CertType_Sig##sig##_PubKeyRsa4096 : \
|
||||
(pub_key_type == CertPubKeyType_Rsa2048 ? CertType_Sig##sig##_PubKeyRsa2048 : CertType_Sig##sig##_PubKeyEcsda240))
|
||||
|
||||
/* Global variables. */
|
||||
|
||||
|
@ -88,6 +89,52 @@ void certFreeCertificateChain(CertificateChain *chain)
|
|||
chain->certs = NULL;
|
||||
}
|
||||
|
||||
CertCommonBlock *certGetCommonBlockFromCertificate(Certificate *cert)
|
||||
{
|
||||
if (!cert || cert->type == CertType_None || cert->type > CertType_SigEcsda240_PubKeyEcsda240 || cert->size < CERT_MIN_SIZE || cert->size > CERT_MAX_SIZE)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CertCommonBlock *cert_common_blk = NULL;
|
||||
|
||||
switch(cert->type)
|
||||
{
|
||||
case CertType_SigRsa4096_PubKeyRsa4096:
|
||||
cert_common_blk = &(((CertSigRsa4096PubKeyRsa4096*)cert->data)->cert_common_blk);
|
||||
break;
|
||||
case CertType_SigRsa4096_PubKeyRsa2048:
|
||||
cert_common_blk = &(((CertSigRsa4096PubKeyRsa2048*)cert->data)->cert_common_blk);
|
||||
break;
|
||||
case CertType_SigRsa4096_PubKeyEcsda240:
|
||||
cert_common_blk = &(((CertSigRsa4096PubKeyEcsda240*)cert->data)->cert_common_blk);
|
||||
break;
|
||||
case CertType_SigRsa2048_PubKeyRsa4096:
|
||||
cert_common_blk = &(((CertSigRsa2048PubKeyRsa4096*)cert->data)->cert_common_blk);
|
||||
break;
|
||||
case CertType_SigRsa2048_PubKeyRsa2048:
|
||||
cert_common_blk = &(((CertSigRsa2048PubKeyRsa2048*)cert->data)->cert_common_blk);
|
||||
break;
|
||||
case CertType_SigRsa2048_PubKeyEcsda240:
|
||||
cert_common_blk = &(((CertSigRsa2048PubKeyEcsda240*)cert->data)->cert_common_blk);
|
||||
break;
|
||||
case CertType_SigEcsda240_PubKeyRsa4096:
|
||||
cert_common_blk = &(((CertSigEcsda240PubKeyRsa4096*)cert->data)->cert_common_blk);
|
||||
break;
|
||||
case CertType_SigEcsda240_PubKeyRsa2048:
|
||||
cert_common_blk = &(((CertSigEcsda240PubKeyRsa2048*)cert->data)->cert_common_blk);
|
||||
break;
|
||||
case CertType_SigEcsda240_PubKeyEcsda240:
|
||||
cert_common_blk = &(((CertSigEcsda240PubKeyEcsda240*)cert->data)->cert_common_blk);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return cert_common_blk;
|
||||
}
|
||||
|
||||
u8 *certGenerateRawCertificateChainBySignatureIssuer(const char *issuer, u64 *out_size)
|
||||
{
|
||||
if (!issuer || !strlen(issuer) || !out_size)
|
||||
|
@ -183,7 +230,7 @@ static bool _certRetrieveCertificateByName(Certificate *dst, const char *name)
|
|||
}
|
||||
|
||||
dst->type = certGetCertificateType(dst->data, dst->size);
|
||||
if (dst->type == CertType_Invalid)
|
||||
if (dst->type == CertType_None)
|
||||
{
|
||||
LOGFILE("Invalid certificate type for \"%s\"!", name);
|
||||
return false;
|
||||
|
@ -197,13 +244,13 @@ static u8 certGetCertificateType(const void *data, u64 data_size)
|
|||
if (!data || data_size < CERT_MIN_SIZE || data_size > CERT_MAX_SIZE)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return CertType_Invalid;
|
||||
return CertType_None;
|
||||
}
|
||||
|
||||
u8 type = CertType_Invalid;
|
||||
const u8 *data_u8 = (const u8*)data;
|
||||
u32 sig_type, pub_key_type;
|
||||
u64 offset = 0;
|
||||
u8 type = CertType_None;
|
||||
const u8 *data_u8 = (const u8*)data;
|
||||
u32 sig_type = 0, pub_key_type = 0;
|
||||
|
||||
memcpy(&sig_type, data_u8, sizeof(u32));
|
||||
sig_type = __builtin_bswap32(sig_type);
|
||||
|
@ -227,14 +274,14 @@ static u8 certGetCertificateType(const void *data, u64 data_size)
|
|||
return type;
|
||||
}
|
||||
|
||||
offset += MEMBER_SIZE(CertSigRsa4096PubKeyRsa4096, issuer);
|
||||
offset += MEMBER_SIZE(CertCommonBlock, issuer);
|
||||
|
||||
memcpy(&pub_key_type, data_u8 + offset, sizeof(u32));
|
||||
pub_key_type = __builtin_bswap32(pub_key_type);
|
||||
|
||||
offset += MEMBER_SIZE(CertSigRsa4096PubKeyRsa4096, pub_key_type);
|
||||
offset += MEMBER_SIZE(CertSigRsa4096PubKeyRsa4096, name);
|
||||
offset += MEMBER_SIZE(CertSigRsa4096PubKeyRsa4096, cert_id);
|
||||
offset += MEMBER_SIZE(CertCommonBlock, pub_key_type);
|
||||
offset += MEMBER_SIZE(CertCommonBlock, name);
|
||||
offset += MEMBER_SIZE(CertCommonBlock, cert_id);
|
||||
|
||||
switch(pub_key_type)
|
||||
{
|
||||
|
@ -254,7 +301,7 @@ static u8 certGetCertificateType(const void *data, u64 data_size)
|
|||
|
||||
if (offset != data_size)
|
||||
{
|
||||
LOGFILE("Calculated end offset doesn't match certificate size! 0x%lX != 0x%lX", offset, data_size);
|
||||
LOGFILE("Calculated end offset doesn't match certificate size! (0x%lX != 0x%lX)", offset, data_size);
|
||||
return type;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,16 +26,16 @@
|
|||
#define CERT_MIN_SIZE 0x180 /* Equivalent to sizeof(CertSigEcsda240PubKeyEcsda240) */
|
||||
|
||||
typedef enum {
|
||||
CertType_SigRsa4096_PubKeyRsa4096 = 0,
|
||||
CertType_SigRsa4096_PubKeyRsa2048 = 1,
|
||||
CertType_SigRsa4096_PubKeyEcsda240 = 2,
|
||||
CertType_SigRsa2048_PubKeyRsa4096 = 3,
|
||||
CertType_SigRsa2048_PubKeyRsa2048 = 4,
|
||||
CertType_SigRsa2048_PubKeyEcsda240 = 5,
|
||||
CertType_SigEcsda240_PubKeyRsa4096 = 6,
|
||||
CertType_SigEcsda240_PubKeyRsa2048 = 7,
|
||||
CertType_SigEcsda240_PubKeyEcsda240 = 8,
|
||||
CertType_Invalid = 255
|
||||
CertType_None = 0,
|
||||
CertType_SigRsa4096_PubKeyRsa4096 = 1,
|
||||
CertType_SigRsa4096_PubKeyRsa2048 = 2,
|
||||
CertType_SigRsa4096_PubKeyEcsda240 = 3,
|
||||
CertType_SigRsa2048_PubKeyRsa4096 = 4,
|
||||
CertType_SigRsa2048_PubKeyRsa2048 = 5,
|
||||
CertType_SigRsa2048_PubKeyEcsda240 = 6,
|
||||
CertType_SigEcsda240_PubKeyRsa4096 = 7,
|
||||
CertType_SigEcsda240_PubKeyRsa2048 = 8,
|
||||
CertType_SigEcsda240_PubKeyEcsda240 = 9
|
||||
} CertType;
|
||||
|
||||
/// Always stored using big endian byte order.
|
||||
|
@ -62,92 +62,73 @@ typedef struct {
|
|||
u8 padding[0x3C];
|
||||
} CertPublicKeyBlockEcsda240;
|
||||
|
||||
/// Placed after the certificate signature block.
|
||||
typedef struct {
|
||||
SignatureBlockRsa4096 sig_block; ///< sig_type field is stored using big endian byte order.
|
||||
char issuer[0x40];
|
||||
u32 pub_key_type; ///< CertPubKeyType_Rsa4096.
|
||||
u32 pub_key_type;
|
||||
char name[0x40];
|
||||
u32 cert_id;
|
||||
} CertCommonBlock;
|
||||
|
||||
typedef struct {
|
||||
SignatureBlockRsa4096 sig_block; ///< sig_type field is stored using big endian byte order.
|
||||
CertCommonBlock cert_common_blk; ///< pub_key_type field must be CertPubKeyType_Rsa4096.
|
||||
CertPublicKeyBlockRsa4096 pub_key_block;
|
||||
} CertSigRsa4096PubKeyRsa4096;
|
||||
|
||||
typedef struct {
|
||||
SignatureBlockRsa4096 sig_block; ///< sig_type field is stored using big endian byte order.
|
||||
char issuer[0x40];
|
||||
u32 pub_key_type; ///< CertPubKeyType_Rsa2048.
|
||||
char name[0x40];
|
||||
u32 cert_id;
|
||||
CertCommonBlock cert_common_blk; ///< pub_key_type field must be CertPubKeyType_Rsa2048.
|
||||
CertPublicKeyBlockRsa2048 pub_key_block;
|
||||
} CertSigRsa4096PubKeyRsa2048;
|
||||
|
||||
typedef struct {
|
||||
SignatureBlockRsa4096 sig_block; ///< sig_type field is stored using big endian byte order.
|
||||
char issuer[0x40];
|
||||
u32 pub_key_type; ///< CertPubKeyType_Ecsda240.
|
||||
char name[0x40];
|
||||
u32 cert_id;
|
||||
CertCommonBlock cert_common_blk; ///< pub_key_type field must be CertPubKeyType_Ecsda240.
|
||||
CertPublicKeyBlockEcsda240 pub_key_block;
|
||||
} CertSigRsa4096PubKeyEcsda240;
|
||||
|
||||
typedef struct {
|
||||
SignatureBlockRsa2048 sig_block; ///< sig_type field is stored using big endian byte order.
|
||||
char issuer[0x40];
|
||||
u32 pub_key_type; ///< CertPubKeyType_Rsa4096.
|
||||
char name[0x40];
|
||||
u32 cert_id;
|
||||
CertCommonBlock cert_common_blk; ///< pub_key_type field must be CertPubKeyType_Rsa4096.
|
||||
CertPublicKeyBlockRsa4096 pub_key_block;
|
||||
} CertSigRsa2048PubKeyRsa4096;
|
||||
|
||||
typedef struct {
|
||||
SignatureBlockRsa2048 sig_block; ///< sig_type field is stored using big endian byte order.
|
||||
char issuer[0x40];
|
||||
u32 pub_key_type; ///< CertPubKeyType_Rsa2048.
|
||||
char name[0x40];
|
||||
u32 cert_id;
|
||||
CertCommonBlock cert_common_blk; ///< pub_key_type field must be CertPubKeyType_Rsa2048.
|
||||
CertPublicKeyBlockRsa2048 pub_key_block;
|
||||
} CertSigRsa2048PubKeyRsa2048;
|
||||
|
||||
typedef struct {
|
||||
SignatureBlockRsa2048 sig_block; ///< sig_type field is stored using big endian byte order.
|
||||
char issuer[0x40];
|
||||
u32 pub_key_type; ///< CertPubKeyType_Ecsda240.
|
||||
char name[0x40];
|
||||
u32 cert_id;
|
||||
CertCommonBlock cert_common_blk; ///< pub_key_type field must be CertPubKeyType_Ecsda240.
|
||||
CertPublicKeyBlockEcsda240 pub_key_block;
|
||||
} CertSigRsa2048PubKeyEcsda240;
|
||||
|
||||
typedef struct {
|
||||
SignatureBlockEcsda240 sig_block; ///< sig_type field is stored using big endian byte order.
|
||||
char issuer[0x40];
|
||||
u32 pub_key_type; ///< CertPubKeyType_Rsa4096.
|
||||
char name[0x40];
|
||||
u32 cert_id;
|
||||
CertCommonBlock cert_common_blk; ///< pub_key_type field must be CertPubKeyType_Rsa4096.
|
||||
CertPublicKeyBlockRsa4096 pub_key_block;
|
||||
} CertSigEcsda240PubKeyRsa4096;
|
||||
|
||||
typedef struct {
|
||||
SignatureBlockEcsda240 sig_block; ///< sig_type field is stored using big endian byte order.
|
||||
char issuer[0x40];
|
||||
u32 pub_key_type; ///< CertPubKeyType_Rsa2048.
|
||||
char name[0x40];
|
||||
u32 cert_id;
|
||||
CertCommonBlock cert_common_blk; ///< pub_key_type field must be CertPubKeyType_Rsa2048.
|
||||
CertPublicKeyBlockRsa2048 pub_key_block;
|
||||
} CertSigEcsda240PubKeyRsa2048;
|
||||
|
||||
typedef struct {
|
||||
SignatureBlockEcsda240 sig_block; ///< sig_type field is stored using big endian byte order.
|
||||
char issuer[0x40];
|
||||
u32 pub_key_type; ///< CertPubKeyType_Ecsda240.
|
||||
char name[0x40];
|
||||
u32 cert_id;
|
||||
CertCommonBlock cert_common_blk; ///< pub_key_type field must be CertPubKeyType_Ecsda240.
|
||||
CertPublicKeyBlockEcsda240 pub_key_block;
|
||||
} CertSigEcsda240PubKeyEcsda240;
|
||||
|
||||
/// Used to store certificate type, size and raw data.
|
||||
typedef struct {
|
||||
u8 type; ///< CertType.
|
||||
u64 size;
|
||||
u8 data[CERT_MAX_SIZE];
|
||||
u64 size; ///< Raw certificate size.
|
||||
u8 data[CERT_MAX_SIZE]; ///< Raw certificate data.
|
||||
} Certificate;
|
||||
|
||||
/// Used to store two or more certificates.
|
||||
|
@ -161,6 +142,9 @@ bool certRetrieveCertificateByName(Certificate *dst, const char *name);
|
|||
bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer);
|
||||
void certFreeCertificateChain(CertificateChain *chain);
|
||||
|
||||
/// Retrieves the common block from an input Certificate.
|
||||
CertCommonBlock *certGetCommonBlockFromCertificate(Certificate *cert);
|
||||
|
||||
/// Returns a pointer to a heap allocated buffer that must be freed by the user.
|
||||
u8 *certGenerateRawCertificateChainBySignatureIssuer(const char *issuer, u64 *out_size);
|
||||
|
||||
|
|
|
@ -41,9 +41,6 @@
|
|||
#define GAMECARD_CAPACITY_16GiB (u64)0x400000000
|
||||
#define GAMECARD_CAPACITY_32GiB (u64)0x800000000
|
||||
|
||||
#define GAMECARD_HFS_PARTITION_NAME(x) ((x) == GameCardHashFileSystemPartitionType_Update ? "update" : ((x) == GameCardHashFileSystemPartitionType_Logo ? "logo" : \
|
||||
((x) == GameCardHashFileSystemPartitionType_Normal ? "normal" : ((x) == GameCardHashFileSystemPartitionType_Secure ? "secure" : "unknown"))))
|
||||
|
||||
/* Type definitions. */
|
||||
|
||||
typedef enum {
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
|
||||
#define GAMECARD_MEDIA_UNIT_SIZE 0x200
|
||||
|
||||
#define GAMECARD_HFS_PARTITION_NAME(x) ((x) == GameCardHashFileSystemPartitionType_Update ? "update" : ((x) == GameCardHashFileSystemPartitionType_Logo ? "logo" : \
|
||||
((x) == GameCardHashFileSystemPartitionType_Normal ? "normal" : ((x) == GameCardHashFileSystemPartitionType_Secure ? "secure" : "unknown"))))
|
||||
|
||||
typedef enum {
|
||||
GameCardKekIndex_Version0 = 0,
|
||||
GameCardKekIndex_VersionForDev = 1
|
||||
|
|
|
@ -188,12 +188,6 @@ const u8 *keysGetNcaHeaderKey(void)
|
|||
|
||||
const u8 *keysGetKeyAreaEncryptionKeySource(u8 kaek_index)
|
||||
{
|
||||
if (kaek_index > NcaKeyAreaEncryptionKeyIndex_System)
|
||||
{
|
||||
LOGFILE("Invalid KAEK index! (0x%02X)", kaek_index);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const u8 *ptr = NULL;
|
||||
|
||||
switch(kaek_index)
|
||||
|
@ -208,6 +202,7 @@ const u8 *keysGetKeyAreaEncryptionKeySource(u8 kaek_index)
|
|||
ptr = (const u8*)(g_ncaKeyset.key_area_key_system_source);
|
||||
break;
|
||||
default:
|
||||
LOGFILE("Invalid KAEK index! (0x%02X)", kaek_index);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -209,6 +209,24 @@ int main(int argc, char *argv[])
|
|||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tikConvertPersonalizedTicketToCommonTicket(&tik);
|
||||
|
||||
printf("common tik generated\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/common_tik.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&tik, 1, sizeof(Ticket), tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("common tik saved\n");
|
||||
} else {
|
||||
printf("common tik not saved\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tik_common_blk = tikGetCommonBlockFromTicket(&tik);
|
||||
|
||||
if (tik_common_blk)
|
||||
|
|
54
source/nca.c
|
@ -21,6 +21,7 @@
|
|||
#include "nca.h"
|
||||
#include "keys.h"
|
||||
#include "rsa.h"
|
||||
#include "gamecard.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* Global variables. */
|
||||
|
@ -32,7 +33,7 @@ static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = {
|
|||
|
||||
/* Function prototypes. */
|
||||
|
||||
static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx);
|
||||
static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx);
|
||||
static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset);
|
||||
static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset);
|
||||
|
||||
|
@ -68,7 +69,30 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src,
|
|||
return i;
|
||||
}
|
||||
|
||||
bool ncaRead(NcaContext *ctx, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!ctx || (ctx->storage_id != NcmStorageId_GameCard && !ctx->ncm_storage) || (ctx->storage_id == NcmStorageId_GameCard && !ctx->gamecard_offset) || !out || !read_size || \
|
||||
offset >= ctx->size || (offset + read_size) > ctx->size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Result rc = 0;
|
||||
bool ret = false;
|
||||
|
||||
if (ctx->storage_id == NcmStorageId_GameCard)
|
||||
{
|
||||
ret = gamecardRead(out, read_size, ctx->gamecard_offset + offset);
|
||||
if (!ret) LOGFILE("Failed to read 0x%lX bytes block at offset 0x%lX from NCA \"%s\"! (gamecard)", read_size, offset, ctx->id_str);
|
||||
} else {
|
||||
rc = ncmContentStorageReadContentIdFile(ctx->ncm_storage, out, read_size, &(ctx->id), offset);
|
||||
ret = R_SUCCEEDED(rc);
|
||||
if (!ret) LOGFILE("Failed to read 0x%lX bytes block at offset 0x%lX from NCA \"%s\"! (0x%08X) (ncm)", read_size, offset, ctx->id_str, rc);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -121,7 +145,7 @@ bool ncaDecryptKeyArea(NcaContext *ctx)
|
|||
|
||||
for(u8 i = 0; i < key_count; i++)
|
||||
{
|
||||
rc = splCryptoGenerateAesKey(tmp_kek, &(ctx->header.encrypted_keys[i]), &(ctx->decrypted_keys[i]));
|
||||
rc = splCryptoGenerateAesKey(tmp_kek, ctx->header.encrypted_keys[i].key, ctx->decrypted_keys[i].key);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOGFILE("splCryptoGenerateAesKey failed! (0x%08X)", rc);
|
||||
|
@ -161,7 +185,7 @@ bool ncaEncryptKeyArea(NcaContext *ctx)
|
|||
key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4);
|
||||
|
||||
aes128ContextCreate(&key_area_ctx, kaek, true);
|
||||
for(u8 i = 0; i < key_count; i++) aes128EncryptBlock(&key_area_ctx, &(ctx->header.encrypted_keys[i]), &(ctx->decrypted_keys[i]));
|
||||
for(u8 i = 0; i < key_count; i++) aes128EncryptBlock(&key_area_ctx, ctx->header.encrypted_keys[i].key, ctx->decrypted_keys[i].key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -176,6 +200,7 @@ bool ncaDecryptHeader(NcaContext *ctx)
|
|||
|
||||
u32 i, magic = 0;
|
||||
size_t crypt_res = 0;
|
||||
u64 fs_header_offset = 0;
|
||||
const u8 *header_key = NULL;
|
||||
Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0};
|
||||
|
||||
|
@ -231,18 +256,27 @@ bool ncaDecryptHeader(NcaContext *ctx)
|
|||
return false;
|
||||
}
|
||||
|
||||
aes128XtsContextCreate(&nca0_fs_header_ctx, &(ctx->decrypted_keys[0]), &(ctx->decrypted_keys[1]), false);
|
||||
aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_keys[0].key, ctx->decrypted_keys[1].key, false);
|
||||
|
||||
for(i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
||||
{
|
||||
if (!ctx->header.fs_entries[i].enable_entry) continue;
|
||||
|
||||
/* FS headers are not part of NCA0 headers */
|
||||
fs_header_offset = NCA_FS_ENTRY_BLOCK_OFFSET(ctx->header.fs_entries[i].start_block_offset);
|
||||
if (!ncaRead(ctx, &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, fs_header_offset))
|
||||
{
|
||||
LOGFILE("Failed to read NCA0 FS section header #%u at offset 0x%lX!", i, fs_header_offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&nca0_fs_header_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, (fs_header_offset - 0x400) >> 9, \
|
||||
NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != NCA_FS_HEADER_LENGTH)
|
||||
{
|
||||
LOGFILE("Error decrypting NCA0 FS section header #%u!", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -332,7 +366,7 @@ bool ncaEncryptHeader(NcaContext *ctx)
|
|||
|
||||
|
||||
|
||||
static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx)
|
||||
static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx || ctx->format_version != NcaVersion_Nca0) return false;
|
||||
|
||||
|
|
19
source/nca.h
|
@ -35,6 +35,7 @@
|
|||
#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR" */
|
||||
|
||||
#define NCA_FS_ENTRY_BLOCK_SIZE 0x200
|
||||
#define NCA_FS_ENTRY_BLOCK_OFFSET(x) ((x) * NCA_FS_ENTRY_BLOCK_SIZE)
|
||||
|
||||
#define NCA_AES_XTS_SECTOR_SIZE 0x200
|
||||
|
||||
|
@ -98,7 +99,7 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
u8 key[0x10];
|
||||
} NcaEncryptedKey;
|
||||
} NcaKey;
|
||||
|
||||
typedef enum {
|
||||
NcaFsType_RomFs = 0,
|
||||
|
@ -235,7 +236,7 @@ typedef struct {
|
|||
FsRightsId rights_id; ///< Used for titlekey crypto.
|
||||
NcaFsEntry fs_entries[4]; ///< Start and end offsets for each NCA FS section.
|
||||
NcaFsHash fs_hashes[4]; ///< SHA-256 hashes calculated over each NCA FS section header.
|
||||
NcaEncryptedKey encrypted_keys[4]; ///< Only the encrypted key at index #2 is used. The other three are zero filled before the key area is encrypted.
|
||||
NcaKey encrypted_keys[4]; ///< Only the encrypted key at index #2 is used. The other three are zero filled before the key area is encrypted.
|
||||
u8 reserved_2[0xC0];
|
||||
NcaFsHeader fs_headers[4]; /// NCA FS section headers.
|
||||
} NcaHeader;
|
||||
|
@ -278,10 +279,22 @@ typedef struct {
|
|||
bool rights_id_available;
|
||||
NcaHeader header;
|
||||
bool dirty_header;
|
||||
NcaEncryptedKey decrypted_keys[4];
|
||||
NcaKey decrypted_keys[4];
|
||||
NcaFsContext fs_contexts[4];
|
||||
} NcaContext;
|
||||
|
||||
/// Reads raw encrypted data from a NCA using a NCA context with the 'storage_id', 'id', 'id_str' and 'size' elements filled beforehand.
|
||||
/// If 'storage_id' == NcmStorageId_GameCard, the 'gamecard_offset' element should hold a value greater than zero.
|
||||
/// If 'storage_id' != NcmStorageId_GameCard, the 'ncm_storage' element should point to a valid NcmContentStorage instance.
|
||||
bool ncaRead(NcaContext *ctx, void *out, u64 read_size, u64 offset);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static inline void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out)
|
||||
{
|
||||
if (!size || !out) return;
|
||||
|
|
|
@ -120,12 +120,12 @@ bool servicesCheckRunningServiceByName(const char *name)
|
|||
{
|
||||
if (!name || !strlen(name)) return false;
|
||||
|
||||
Handle handle;
|
||||
Handle handle = INVALID_HANDLE;
|
||||
SmServiceName service_name = smEncodeName(name);
|
||||
Result rc = smRegisterService(&handle, service_name, false, 1);
|
||||
bool running = R_FAILED(rc);
|
||||
|
||||
svcCloseHandle(handle);
|
||||
if (handle != INVALID_HANDLE) svcCloseHandle(handle);
|
||||
|
||||
if (!running) smUnregisterService(service_name);
|
||||
|
||||
|
|
62
source/tik.c
|
@ -66,7 +66,7 @@ static TikCommonBlock *tikGetCommonBlockFromMemoryBuffer(void *data);
|
|||
static bool tikGetTitleKeyFromTicketCommonBlock(void *dst, const TikCommonBlock *tik_common_blk);
|
||||
static bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_generation);
|
||||
|
||||
static u8 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id);
|
||||
static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out);
|
||||
static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized);
|
||||
|
||||
static bool tikGetTicketTypeAndSize(const void *data, u64 data_size, u8 *out_type, u64 *out_size);
|
||||
|
@ -86,7 +86,7 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam
|
|||
return false;
|
||||
}
|
||||
|
||||
tik_common_blk = tikGetCommonBlockFromMemoryBuffer(dst->data);
|
||||
tik_common_blk = tikGetCommonBlockFromTicket(dst);
|
||||
if (!tik_common_blk)
|
||||
{
|
||||
LOGFILE("Unable to retrieve common block from ticket!");
|
||||
|
@ -112,23 +112,40 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam
|
|||
|
||||
TikCommonBlock *tikGetCommonBlockFromTicket(Ticket *tik)
|
||||
{
|
||||
if (!tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE)
|
||||
if (!tik || tik->type == TikType_None || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE || tik->size > TIK_MAX_SIZE)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return tikGetCommonBlockFromMemoryBuffer(tik->data);
|
||||
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;
|
||||
case TikType_SigEcsda240:
|
||||
tik_common_blk = &(((TikSigEcsda240*)tik->data)->tik_common_blk);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return tik_common_blk;
|
||||
}
|
||||
|
||||
void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik)
|
||||
{
|
||||
if (!tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE) return;
|
||||
if (!tik || tik->type == TikType_None || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE || tik->size > TIK_MAX_SIZE) return;
|
||||
|
||||
bool dev_cert = false;
|
||||
TikCommonBlock *tik_common_blk = NULL;
|
||||
|
||||
tik_common_blk = tikGetCommonBlockFromMemoryBuffer(tik->data);
|
||||
tik_common_blk = tikGetCommonBlockFromTicket(tik);
|
||||
if (!tik_common_blk || tik_common_blk->titlekey_type != TikTitleKeyType_Personalized) return;
|
||||
|
||||
switch(tik->type)
|
||||
|
@ -220,19 +237,19 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
|
|||
}
|
||||
|
||||
u32 i;
|
||||
u8 titlekey_type = 0;
|
||||
|
||||
save_ctx_t *save_ctx = NULL;
|
||||
allocation_table_storage_ctx_t fat_storage = {0};
|
||||
u64 ticket_bin_size = 0;
|
||||
|
||||
u64 buf_size = (TIK_MAX_SIZE * 0x10); /* 0x4000 */
|
||||
u64 buf_size = (TIK_MAX_SIZE * 0x10);
|
||||
u64 br = 0, total_br = 0;
|
||||
u8 *ticket_bin_buf = NULL;
|
||||
|
||||
bool found_tik = false, success = false;
|
||||
|
||||
u8 titlekey_type = tikGetTitleKeyTypeFromRightsId(id);
|
||||
if (titlekey_type == TikTitleKeyType_Invalid)
|
||||
if (!tikGetTitleKeyTypeFromRightsId(id, &titlekey_type))
|
||||
{
|
||||
LOGFILE("Unable to retrieve ticket titlekey type!");
|
||||
return false;
|
||||
|
@ -326,9 +343,9 @@ static TikCommonBlock *tikGetCommonBlockFromMemoryBuffer(void *data)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
u32 sig_type = 0;
|
||||
u8 *data_u8 = (u8*)data;
|
||||
TikCommonBlock *tik_common_blk = NULL;
|
||||
u32 sig_type = 0;
|
||||
|
||||
memcpy(&sig_type, data_u8, sizeof(u32));
|
||||
|
||||
|
@ -426,27 +443,27 @@ static bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_g
|
|||
return true;
|
||||
}
|
||||
|
||||
static u8 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id)
|
||||
static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out)
|
||||
{
|
||||
if (!id)
|
||||
if (!id || !out)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return TikTitleKeyType_Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 type = TikTitleKeyType_Invalid;
|
||||
u32 count;
|
||||
FsRightsId *rights_ids;
|
||||
bool found = false;
|
||||
|
||||
for(u8 i = 0; i < 2; i++)
|
||||
{
|
||||
count = 0;
|
||||
rights_ids = NULL;
|
||||
|
||||
if (!tikRetrieveRightsIdsByTitleKeyType(&rights_ids, &count, (bool)i))
|
||||
if (!tikRetrieveRightsIdsByTitleKeyType(&rights_ids, &count, i == 1))
|
||||
{
|
||||
LOGFILE("Unable to retrieve %s rights IDs!", i == 0 ? "common" : "personalized");
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!count) continue;
|
||||
|
@ -455,17 +472,18 @@ static u8 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id)
|
|||
{
|
||||
if (!memcmp(rights_ids[j].c, id->c, 0x10))
|
||||
{
|
||||
type = i; /* TikTitleKeyType_Common or TikTitleKeyType_Personalized */
|
||||
*out = i; /* TikTitleKeyType_Common or TikTitleKeyType_Personalized */
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(rights_ids);
|
||||
|
||||
if (type != TikTitleKeyType_Invalid) break;
|
||||
if (found) break;
|
||||
}
|
||||
|
||||
return type;
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized)
|
||||
|
@ -523,11 +541,11 @@ static bool tikGetTicketTypeAndSize(const void *data, u64 data_size, u8 *out_typ
|
|||
return false;
|
||||
}
|
||||
|
||||
u8 type = TikType_Invalid;
|
||||
const u8 *data_u8 = (const u8*)data;
|
||||
const TikCommonBlock *tik_common_blk = NULL;
|
||||
u32 sig_type = 0;
|
||||
u64 offset = 0;
|
||||
u8 type = TikType_None;
|
||||
const u8 *data_u8 = (const u8*)data;
|
||||
const TikCommonBlock *tik_common_blk = NULL;
|
||||
|
||||
memcpy(&sig_type, data_u8, sizeof(u32));
|
||||
|
||||
|
|
15
source/tik.h
|
@ -22,20 +22,19 @@
|
|||
#include <switch.h>
|
||||
#include "signature.h"
|
||||
|
||||
#define TIK_MAX_SIZE 0x400 /* Max ticket entry size in the ES system savefiles */
|
||||
#define TIK_MAX_SIZE 0x400 /* Max ticket entry size in the ES ticket system savedata file */
|
||||
#define TIK_MIN_SIZE 0x200 /* Equivalent to sizeof(TikSigEcsda240) - assuming no ESv2 records are available */
|
||||
|
||||
typedef enum {
|
||||
TikType_SigRsa4096 = 0,
|
||||
TikType_SigRsa2048 = 1,
|
||||
TikType_SigEcsda240 = 2,
|
||||
TikType_Invalid = 255
|
||||
TikType_None = 0,
|
||||
TikType_SigRsa4096 = 1,
|
||||
TikType_SigRsa2048 = 2,
|
||||
TikType_SigEcsda240 = 3
|
||||
} TikType;
|
||||
|
||||
typedef enum {
|
||||
TikTitleKeyType_Common = 0,
|
||||
TikTitleKeyType_Personalized = 1,
|
||||
TikTitleKeyType_Invalid = 255
|
||||
TikTitleKeyType_Personalized = 1
|
||||
} TikTitleKeyType;
|
||||
|
||||
typedef enum {
|
||||
|
@ -121,7 +120,7 @@ typedef struct {
|
|||
u8 dec_titlekey[0x10]; ///< Titlekey without titlekek crypto. Ready to use for NCA FS section decryption.
|
||||
} Ticket;
|
||||
|
||||
/// Retrieves a ticket from either the secure hash FS partition from the inserted gamecard or ES ticket savedata using a Rights ID value.
|
||||
/// Retrieves a ticket from either the ES ticket system savedata file (eMMC BIS System partition) or the secure hash FS partition from an inserted gamecard, using a Rights ID value.
|
||||
/// Titlekey is also RSA-OAEP unwrapped (if needed) and titlekek decrypted right away.
|
||||
bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gamecard);
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ static AppletHookCookie g_systemOverclockCookie = {0};
|
|||
static Mutex g_logfileMutex = 0;
|
||||
|
||||
static FsStorage g_emmcBisSystemPartitionStorage = {0};
|
||||
static FATFS *g_emmcBisSystemPartitionFs = NULL;
|
||||
static FATFS *g_emmcBisSystemPartitionFatFsObj = NULL;
|
||||
|
||||
/* Function prototypes. */
|
||||
|
||||
|
@ -200,11 +200,11 @@ void utilsGenerateHexStringFromData(char *dst, size_t dst_size, const void *src,
|
|||
|
||||
for(i = 0, j = 0; i < src_size; i++)
|
||||
{
|
||||
char nib1 = ((src_u8[i] >> 4) & 0xF);
|
||||
char nib2 = (src_u8[i] & 0xF);
|
||||
char h_nib = ((src_u8[i] >> 4) & 0xF);
|
||||
char l_nib = (src_u8[i] & 0xF);
|
||||
|
||||
dst[j++] = (nib1 + (nib1 < 0xA ? 0x30 : 0x57));
|
||||
dst[j++] = (nib2 + (nib2 < 0xA ? 0x30 : 0x57));
|
||||
dst[j++] = (h_nib + (h_nib < 0xA ? 0x30 : 0x57));
|
||||
dst[j++] = (l_nib + (l_nib < 0xA ? 0x30 : 0x57));
|
||||
}
|
||||
|
||||
dst[j] = '\0';
|
||||
|
@ -257,14 +257,14 @@ static bool utilsMountEmmcBisSystemPartitionStorage(void)
|
|||
return false;
|
||||
}
|
||||
|
||||
g_emmcBisSystemPartitionFs = calloc(1, sizeof(FATFS));
|
||||
if (!g_emmcBisSystemPartitionFs)
|
||||
g_emmcBisSystemPartitionFatFsObj = calloc(1, sizeof(FATFS));
|
||||
if (!g_emmcBisSystemPartitionFatFsObj)
|
||||
{
|
||||
LOGFILE("Unable to allocate memory for FatFs object!");
|
||||
return false;
|
||||
}
|
||||
|
||||
fr = f_mount(g_emmcBisSystemPartitionFs, BIS_SYSTEM_PARTITION_MOUNT_NAME, 1);
|
||||
fr = f_mount(g_emmcBisSystemPartitionFatFsObj, BIS_SYSTEM_PARTITION_MOUNT_NAME, 1);
|
||||
if (fr != FR_OK)
|
||||
{
|
||||
LOGFILE("Failed to mount eMMC BIS System partition! (%u)", fr);
|
||||
|
@ -276,11 +276,11 @@ static bool utilsMountEmmcBisSystemPartitionStorage(void)
|
|||
|
||||
static void utilsUnmountEmmcBisSystemPartitionStorage(void)
|
||||
{
|
||||
if (g_emmcBisSystemPartitionFs)
|
||||
if (g_emmcBisSystemPartitionFatFsObj)
|
||||
{
|
||||
f_unmount(BIS_SYSTEM_PARTITION_MOUNT_NAME);
|
||||
free(g_emmcBisSystemPartitionFs);
|
||||
g_emmcBisSystemPartitionFs = NULL;
|
||||
free(g_emmcBisSystemPartitionFatFsObj);
|
||||
g_emmcBisSystemPartitionFatFsObj = NULL;
|
||||
}
|
||||
|
||||
if (serviceIsActive(&(g_emmcBisSystemPartitionStorage.s)))
|
||||
|
|