mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 18:26:39 +00:00
nca: skip hash layer crypto operations if needed.
ncaInitializeContext(), _ncaReadFsSection() and ncaGenerateHashDataPatch() were all updated to reflect this change. SHA3-256 support is still missing.
This commit is contained in:
parent
8ce253d13d
commit
2c4ddac4c6
4 changed files with 136 additions and 33 deletions
|
@ -253,12 +253,12 @@ NXDT_ASSERT(NcaIntegrityMetaInfo, 0xE0);
|
|||
typedef struct {
|
||||
union {
|
||||
struct {
|
||||
///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs and NCA0 NcaFsType_RomFs).
|
||||
///< Used for NcaHashType_HierarchicalSha256 and NcaHashType_HierarchicalSha3256 (NcaFsType_PartitionFs and NCA0 NcaFsType_RomFs).
|
||||
NcaHierarchicalSha256Data hierarchical_sha256_data;
|
||||
u8 reserved_1[0x80];
|
||||
};
|
||||
struct {
|
||||
///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs).
|
||||
///< Used if NcaHashType_HierarchicalIntegrity and NcaHashType_HierarchicalIntegritySha3 (NcaFsType_RomFs).
|
||||
NcaIntegrityMetaInfo integrity_meta_info;
|
||||
u8 reserved_2[0x18];
|
||||
};
|
||||
|
@ -366,13 +366,15 @@ typedef struct {
|
|||
NcaFsHeader header; ///< Plaintext NCA FS section header.
|
||||
NcaFsHeader encrypted_header; ///< Encrypted NCA FS section header. If the plaintext NCA FS section header is modified, this will hold an encrypted copy of it.
|
||||
///< Otherwise, this holds the unmodified, encrypted NCA FS section header.
|
||||
bool header_written; ///< Set to true after this FS section header has been written to an output dump.
|
||||
u8 section_num;
|
||||
u64 section_offset;
|
||||
u8 section_num; ///< Index within [0 - 3].
|
||||
u64 section_offset; ///< Relative to the start of the NCA content file.
|
||||
u64 section_size;
|
||||
u8 hash_type; ///< NcaHashType.
|
||||
u8 encryption_type; ///< NcaEncryptionType.
|
||||
u8 section_type; ///< NcaFsSectionType.
|
||||
bool skip_hash_layer_crypto; ///< Set to true if hash layer encryption should be skipped while reading section data.
|
||||
u64 last_layer_offset; ///< Relative to the start of the FS section.
|
||||
u64 last_layer_size;
|
||||
u8 ctr[AES_BLOCK_SIZE]; ///< Used to update the AES CTR context IV based on the desired offset.
|
||||
Aes128CtrContext ctr_ctx;
|
||||
Aes128XtsContext xts_decrypt_ctx;
|
||||
|
@ -384,6 +386,9 @@ typedef struct {
|
|||
u64 sparse_table_size; ///< header.sparse_info.bucket.size. Placed here for convenience.
|
||||
u8 sparse_ctr[AES_BLOCK_SIZE];
|
||||
Aes128CtrContext sparse_ctr_ctx;
|
||||
|
||||
///< NSP-related fields.
|
||||
bool header_written; ///< Set to true after this FS section header has been written to an output dump.
|
||||
} NcaFsSectionContext;
|
||||
|
||||
typedef enum {
|
||||
|
|
|
@ -963,7 +963,6 @@ static bool gamecardReadSecurityInformation(GameCardSecurityInformation *out)
|
|||
if (memcmp(g_fsProgramMemory.data + offset, &(g_gameCardHeader.package_id), sizeof(g_gameCardHeader.package_id)) != 0) continue;
|
||||
|
||||
sha256CalculateHash(tmp_hash, g_fsProgramMemory.data + offset, sizeof(GameCardInitialData));
|
||||
|
||||
if (!memcmp(tmp_hash, g_gameCardHeader.initial_data_hash, SHA256_HASH_SIZE))
|
||||
{
|
||||
/* Jackpot. */
|
||||
|
|
|
@ -532,7 +532,6 @@ static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info)
|
|||
if ((info->location.data_size - j) < key->size) break;
|
||||
|
||||
sha256CalculateHash(tmp_hash, info->location.data + j, key->size);
|
||||
|
||||
if (!memcmp(tmp_hash, key->hash, SHA256_HASH_SIZE))
|
||||
{
|
||||
/* Jackpot. */
|
||||
|
|
|
@ -194,7 +194,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
/* Check if we're dealing with an invalid start offset or an empty size. */
|
||||
if (fs_ctx->section_offset < sizeof(NcaHeader) || !fs_ctx->section_size) continue;
|
||||
|
||||
/* Determine hash and encryption types. */
|
||||
/* Determine FS section hash type. */
|
||||
fs_ctx->hash_type = fs_ctx->header.hash_type;
|
||||
if (fs_ctx->hash_type == NcaHashType_Auto || fs_ctx->hash_type == NcaHashType_AutoSha3)
|
||||
{
|
||||
|
@ -212,6 +212,9 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
}
|
||||
}
|
||||
|
||||
if (fs_ctx->hash_type == NcaHashType_Auto || fs_ctx->hash_type == NcaHashType_AutoSha3 || fs_ctx->hash_type > NcaHashType_HierarchicalIntegritySha3) continue;
|
||||
|
||||
/* Determine FS section encryption type. */
|
||||
fs_ctx->encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : fs_ctx->header.encryption_type);
|
||||
if (fs_ctx->encryption_type == NcaEncryptionType_Auto)
|
||||
{
|
||||
|
@ -229,9 +232,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
}
|
||||
}
|
||||
|
||||
/* Check if we're dealing with invalid hash/encryption type values. */
|
||||
if (fs_ctx->hash_type == NcaHashType_Auto || fs_ctx->hash_type == NcaHashType_AutoSha3 || fs_ctx->hash_type > NcaHashType_HierarchicalIntegritySha3 || \
|
||||
fs_ctx->encryption_type == NcaEncryptionType_Auto || fs_ctx->encryption_type > NcaEncryptionType_AesCtrExSkipLayerHash) continue;
|
||||
if (fs_ctx->encryption_type == NcaEncryptionType_Auto || fs_ctx->encryption_type > NcaEncryptionType_AesCtrExSkipLayerHash) continue;
|
||||
|
||||
/* Determine FS section type. */
|
||||
if (fs_ctx->header.fs_type == NcaFsType_PartitionFs && (fs_ctx->hash_type == NcaHashType_HierarchicalSha256 || fs_ctx->hash_type == NcaHashType_HierarchicalSha3256))
|
||||
|
@ -248,9 +249,38 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
fs_ctx->section_type = NcaFsSectionType_Nca0RomFs;
|
||||
}
|
||||
|
||||
/* Check if we're dealing with an invalid section type value. */
|
||||
if (fs_ctx->section_type >= NcaFsSectionType_Invalid) continue;
|
||||
|
||||
/* Check if we should skip hash layer decryption while reading this FS section. */
|
||||
fs_ctx->skip_hash_layer_crypto = (fs_ctx->encryption_type == NcaEncryptionType_AesCtrSkipLayerHash || fs_ctx->encryption_type == NcaEncryptionType_AesCtrExSkipLayerHash);
|
||||
if (fs_ctx->skip_hash_layer_crypto)
|
||||
{
|
||||
u32 layer_count = 0;
|
||||
|
||||
if (fs_ctx->hash_type == NcaHashType_HierarchicalSha256 || fs_ctx->hash_type == NcaHashType_HierarchicalSha3256)
|
||||
{
|
||||
layer_count = fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region_count;
|
||||
if (layer_count <= NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT)
|
||||
{
|
||||
NcaRegion *last_region = &(fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region[layer_count - 1]);
|
||||
fs_ctx->last_layer_offset = last_region->offset;
|
||||
fs_ctx->last_layer_size = last_region->size;
|
||||
}
|
||||
} else
|
||||
if (fs_ctx->hash_type == NcaHashType_HierarchicalIntegrity || fs_ctx->hash_type == NcaHashType_HierarchicalIntegritySha3)
|
||||
{
|
||||
layer_count = (fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.max_level_count - 1);
|
||||
if (layer_count == NCA_IVFC_LEVEL_COUNT)
|
||||
{
|
||||
NcaHierarchicalIntegrityVerificationLevelInformation *last_level_info = &(fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[layer_count - 1]);
|
||||
fs_ctx->last_layer_offset = last_level_info->offset;
|
||||
fs_ctx->last_layer_size = last_level_info->size;
|
||||
}
|
||||
} else {
|
||||
fs_ctx->skip_hash_layer_crypto = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if we're dealing with a sparse storage. */
|
||||
if (fs_ctx->has_sparse_layer)
|
||||
{
|
||||
|
@ -573,18 +603,28 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx)
|
|||
{
|
||||
NcaContext *nca_ctx = NULL;
|
||||
const char *str = "Invalid";
|
||||
bool is_exefs = false;
|
||||
|
||||
if (!ctx || !ctx->enabled || !(nca_ctx = (NcaContext*)ctx->nca_ctx)) return str;
|
||||
|
||||
is_exefs = (nca_ctx->content_type == NcmContentType_Program && ctx->section_num == 0);
|
||||
|
||||
switch(ctx->section_type)
|
||||
{
|
||||
case NcaFsSectionType_PartitionFs:
|
||||
str = ((nca_ctx->content_type == NcmContentType_Program && ctx->section_num == 0) ? "ExeFS" : "Partition FS");
|
||||
if (ctx->has_sparse_layer)
|
||||
{
|
||||
str = (is_exefs ? "ExeFS (update required)" : "Partition FS (update required)");
|
||||
} else {
|
||||
str = (is_exefs ? "ExeFS" : "Partition FS");
|
||||
}
|
||||
|
||||
break;
|
||||
case NcaFsSectionType_RomFs:
|
||||
str = "RomFS";
|
||||
str = (ctx->has_sparse_layer ? "RomFS (update required)" : "RomFS");
|
||||
break;
|
||||
case NcaFsSectionType_PatchRomFs:
|
||||
str = "Patch RomFS [BKTR]";
|
||||
str = "Patch RomFS (base required)";
|
||||
break;
|
||||
case NcaFsSectionType_Nca0RomFs:
|
||||
str = "NCA0 RomFS";
|
||||
|
@ -861,6 +901,50 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
|
|||
goto end;
|
||||
}
|
||||
|
||||
/* Check if we're supposed to read a hash layer without encryption. */
|
||||
if (ctx->skip_hash_layer_crypto && ((offset + read_size) <= ctx->last_layer_offset || (ctx->last_layer_offset + ctx->last_layer_size) <= offset || \
|
||||
(offset < ctx->last_layer_offset && (offset + read_size) > ctx->last_layer_offset) || \
|
||||
(ctx->last_layer_offset < offset && (ctx->last_layer_offset + ctx->last_layer_size) < (offset + read_size))))
|
||||
{
|
||||
/* Easy route. Just read the plaintext data we need and bail out. */
|
||||
if ((offset + read_size) <= ctx->last_layer_offset || (ctx->last_layer_offset + ctx->last_layer_size) <= offset)
|
||||
{
|
||||
ret = ncaReadContentFile(nca_ctx, out, read_size, content_offset);
|
||||
if (!ret) LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash layer) (#1).", read_size, content_offset, \
|
||||
nca_ctx->content_id_str, ctx->section_num);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Long, tedious route. Calculate offsets and block sizes. */
|
||||
block_start_offset = content_offset;
|
||||
block_end_offset = ((block_start_offset + read_size) - (offset < ctx->last_layer_offset ? ctx->last_layer_offset : (ctx->last_layer_offset + ctx->last_layer_size)));
|
||||
block_size = (block_end_offset - block_start_offset);
|
||||
|
||||
if (offset < ctx->last_layer_offset)
|
||||
{
|
||||
/* Read plaintext area. */
|
||||
if (!ncaReadContentFile(nca_ctx, out, block_size, block_start_offset))
|
||||
{
|
||||
LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash layer) (#2).", block_size, block_start_offset, \
|
||||
nca_ctx->content_id_str, ctx->section_num);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Read encrypted area. */
|
||||
ret = _ncaReadFsSection(ctx, (u8*)out + block_size, read_size - block_size, offset + block_size);
|
||||
} else {
|
||||
/* Read encrypted area. */
|
||||
if (!_ncaReadFsSection(ctx, out, block_size, offset)) goto end;
|
||||
|
||||
/* Read plaintext area. */
|
||||
ret = ncaReadContentFile(nca_ctx, (u8*)out + block_size, read_size - block_size, block_end_offset);
|
||||
if (!ret) LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash layer) (#3).", read_size - block_size, \
|
||||
block_end_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Optimization for reads from plaintext FS sections or reads that are aligned to the AES-CTR / AES-XTS sector size. */
|
||||
if (ctx->encryption_type == NcaEncryptionType_None || \
|
||||
(ctx->encryption_type == NcaEncryptionType_AesXts && !(content_offset % NCA_AES_XTS_SECTOR_SIZE) && !(read_size % NCA_AES_XTS_SECTOR_SIZE)) || \
|
||||
|
@ -1042,13 +1126,15 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data,
|
|||
|
||||
bool success = false;
|
||||
|
||||
if (!ctx || !ctx->enabled || ctx->has_sparse_layer || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || (!is_integrity_patch && (ctx->header.hash_type != NcaHashType_HierarchicalSha256 || \
|
||||
!ctx->header.hash_data.hierarchical_sha256_data.hash_block_size || !(layer_count = ctx->header.hash_data.hierarchical_sha256_data.hash_region_count) || \
|
||||
layer_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT || !(last_layer_size = ctx->header.hash_data.hierarchical_sha256_data.hash_region[layer_count - 1].size))) || \
|
||||
(is_integrity_patch && (ctx->header.hash_type != NcaHashType_HierarchicalIntegrity || \
|
||||
if (!ctx || !ctx->enabled || ctx->has_sparse_layer || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || (!is_integrity_patch && ((ctx->hash_type != NcaHashType_HierarchicalSha256 && \
|
||||
ctx->hash_type != NcaHashType_HierarchicalSha3256) || !ctx->header.hash_data.hierarchical_sha256_data.hash_block_size || \
|
||||
!(layer_count = ctx->header.hash_data.hierarchical_sha256_data.hash_region_count) || layer_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT || \
|
||||
!(last_layer_size = ctx->header.hash_data.hierarchical_sha256_data.hash_region[layer_count - 1].size))) || \
|
||||
(is_integrity_patch && ((ctx->hash_type != NcaHashType_HierarchicalIntegrity && ctx->hash_type != NcaHashType_HierarchicalIntegritySha3) || \
|
||||
!(layer_count = (ctx->header.hash_data.integrity_meta_info.info_level_hash.max_level_count - 1)) || layer_count != NCA_IVFC_LEVEL_COUNT || \
|
||||
!(last_layer_size = ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1].size))) || !data || !data_size || \
|
||||
(data_offset + data_size) > last_layer_size || !out)
|
||||
(data_offset + data_size) > last_layer_size || !out || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type == NcaEncryptionType_AesCtrEx || \
|
||||
ctx->encryption_type >= NcaEncryptionType_AesCtrExSkipLayerHash)
|
||||
{
|
||||
LOG_MSG("Invalid parameters!");
|
||||
goto end;
|
||||
|
@ -1184,7 +1270,9 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data,
|
|||
sha256CalculateHash(master_hash, cur_layer_block, cur_layer_read_size);
|
||||
}
|
||||
|
||||
/* Reencrypt current layer block. */
|
||||
if (!ctx->skip_hash_layer_crypto || i == layer_count)
|
||||
{
|
||||
/* Reencrypt current layer block (if needed). */
|
||||
cur_layer_patch->data = _ncaGenerateEncryptedFsSectionBlock(ctx, cur_layer_block + cur_layer_read_patch_offset, cur_data_size, cur_layer_offset + cur_data_offset, \
|
||||
&(cur_layer_patch->size), &(cur_layer_patch->offset));
|
||||
if (!cur_layer_patch->data)
|
||||
|
@ -1192,6 +1280,19 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data,
|
|||
LOG_MSG("Failed to generate encrypted 0x%lX bytes long hierarchical layer #%u data block!", cur_data_size, i - 1);
|
||||
goto end;
|
||||
}
|
||||
} else {
|
||||
/* Allocate memory for the data block and copy its information. */
|
||||
cur_layer_patch->data = malloc(cur_data_size);
|
||||
if (!cur_layer_patch->data)
|
||||
{
|
||||
LOG_MSG("Failed to allocate 0x%lX bytes long buffer for hierarchical layer #%u data block!", cur_data_size, i - 1);
|
||||
goto end;
|
||||
}
|
||||
|
||||
memcpy(cur_layer_patch->data, cur_layer_block + cur_layer_read_patch_offset, cur_data_size);
|
||||
cur_layer_patch->size = cur_data_size;
|
||||
cur_layer_patch->offset = (ctx->section_offset + cur_layer_offset + cur_data_offset);
|
||||
}
|
||||
|
||||
/* Free current layer block. */
|
||||
free(cur_layer_block);
|
||||
|
@ -1267,11 +1368,10 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
|||
u8 *out = NULL;
|
||||
bool success = false;
|
||||
|
||||
// TODO: add support for Sha3 layers and SkipLayer crypto types.
|
||||
if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || ctx->has_sparse_layer || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \
|
||||
ctx->hash_type < NcaHashType_HierarchicalSha256 || ctx->hash_type > NcaHashType_HierarchicalIntegrity || ctx->encryption_type == NcaEncryptionType_Auto || \
|
||||
ctx->encryption_type >= NcaEncryptionType_AesCtrEx || ctx->section_type >= NcaFsSectionType_Invalid || !data || !data_size || (data_offset + data_size) > ctx->section_size || \
|
||||
!out_block_size || !out_block_offset)
|
||||
ctx->hash_type <= NcaHashType_None || ctx->hash_type == NcaHashType_AutoSha3 || ctx->hash_type > NcaHashType_HierarchicalIntegritySha3 || \
|
||||
ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type == NcaEncryptionType_AesCtrEx || ctx->encryption_type >= NcaEncryptionType_AesCtrExSkipLayerHash || \
|
||||
ctx->section_type >= NcaFsSectionType_Invalid || !data || !data_size || (data_offset + data_size) > ctx->section_size || !out_block_size || !out_block_offset)
|
||||
{
|
||||
LOG_MSG("Invalid NCA FS section header parameters!");
|
||||
goto end;
|
||||
|
@ -1296,7 +1396,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
|||
/* Optimization for blocks from plaintext FS sections or blocks that are aligned to the AES-CTR / AES-XTS sector size. */
|
||||
if (ctx->encryption_type == NcaEncryptionType_None || \
|
||||
(ctx->encryption_type == NcaEncryptionType_AesXts && !(content_offset % NCA_AES_XTS_SECTOR_SIZE) && !(data_size % NCA_AES_XTS_SECTOR_SIZE)) || \
|
||||
(ctx->encryption_type == NcaEncryptionType_AesCtr && !(content_offset % AES_BLOCK_SIZE) && !(data_size % AES_BLOCK_SIZE)))
|
||||
((ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrSkipLayerHash) && !(content_offset % AES_BLOCK_SIZE) && !(data_size % AES_BLOCK_SIZE)))
|
||||
{
|
||||
/* Allocate memory. */
|
||||
out = malloc(data_size);
|
||||
|
@ -1321,7 +1421,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
|||
goto end;
|
||||
}
|
||||
} else
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr)
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrSkipLayerHash)
|
||||
{
|
||||
aes128CtrUpdatePartialCtr(ctx->ctr, content_offset);
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
|
@ -1373,7 +1473,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
|||
goto end;
|
||||
}
|
||||
} else
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr)
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrSkipLayerHash)
|
||||
{
|
||||
aes128CtrUpdatePartialCtr(ctx->ctr, content_offset);
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
|
|
Loading…
Reference in a new issue