hac2l: auto-load common keys from tickets inside app-fs, support pfs0/nsp as exefs-or-appfs

This commit is contained in:
Michael Scire 2022-03-18 00:49:20 -07:00
parent 1c41ca7fc6
commit 60db38430a
5 changed files with 262 additions and 9 deletions

View File

@ -112,6 +112,8 @@ namespace ams::hactool {
options.file_type = FileType::Xci;
} else if (std::strcmp(arg, "appfs") == 0) {
options.file_type = FileType::AppFs;
} else if (std::strcmp(arg, "pfs") == 0 || std::strcmp(arg, "pfs0") == 0 || std::strcmp(arg, "nsp") == 0) {
options.file_type = FileType::Pfs;
} else {
return false;
}

View File

@ -28,6 +28,109 @@ namespace ams::hactool {
constexpr const char ContentMetaFileNameExtension[] = ".cnmt";
constexpr const char TicketFileNameExtension[] = ".tik";
struct alignas(4) CommonTicketData {
u32 signature_type;
u8 signature_data[0x100];
u8 padding0[0x3C];
char issuer[0x40];
u8 title_key_block[0x100];
u8 format_version;
u8 titlekey_type;
u16 ticket_version;
u8 license_type;
u8 key_generation;
u16 property_mask;
u8 reserved[8];
u8 ticket_id[8];
u8 device_id[8];
u8 rights_id[0x10];
u8 account_id[0x4];
u32 total_section_size;
u32 section_header_offset;
u16 section_header_count;
u16 section_header_entry_size;
};
static_assert(util::is_pod<CommonTicketData>::value);
static_assert(sizeof(CommonTicketData) == 0x2C0);
bool IsValidCommonTicketFormat(const void *data, size_t size) {
/* Check that the data is the right size for a ticket. */
if (size != sizeof(CommonTicketData)) {
return false;
}
/* Check the ticket. */
const auto &ticket = *static_cast<const CommonTicketData *>(data);
/* Check that the ticket is an aes key. */
if (ticket.titlekey_type != 0) {
return false;
}
/* Check that the ticket's rights id isn't all-zero. */
size_t i;
for (i = 0; i < util::size(ticket.rights_id); ++i) {
if (ticket.rights_id[i] != 0) {
break;
}
}
if (i == util::size(ticket.rights_id)) {
return false;
}
/* Check that the ticket is a proper aes-key. */
for (i = 0; i < sizeof(spl::AesKey); ++i) {
if (ticket.title_key_block[i] != 0) {
break;
}
}
if (i == sizeof(spl::AesKey)) {
return false;
}
for (i = sizeof(spl::AesKey); i < util::size(ticket.title_key_block); ++i) {
if (ticket.title_key_block[i] != 0) {
break;
}
}
if (i != util::size(ticket.title_key_block)) {
return false;
}
/* Check that the ticket's section header is proper. */
if (ticket.section_header_offset != sizeof(CommonTicketData)) {
return false;
}
/* Ticket is good enough. */
return true;
}
bool TryLoadKeyFromCommonTicket(fssrv::impl::ExternalKeyManager &km, const void *data, size_t size) {
if (IsValidCommonTicketFormat(data, size)) {
/* Get the ticket. */
const auto &ticket = *static_cast<const CommonTicketData *>(data);
/* Decode the rights id. */
fs::RightsId rights_id = {};
std::memcpy(std::addressof(rights_id), ticket.rights_id, sizeof(rights_id));
/* Decode the key. */
spl::AccessKey access_key = {};
std::memcpy(std::addressof(access_key), ticket.title_key_block, sizeof(access_key));
/* Register with the key manager. */
km.Register(rights_id, access_key);
return true;
} else {
return false;
}
}
Result ReadContentMetaFile(std::unique_ptr<u8[]> *out, size_t *out_size, std::shared_ptr<fs::fsa::IFileSystem> &fs) {
bool found = false;
R_RETURN(fssystem::IterateDirectoryRecursively(fs.get(),
@ -69,9 +172,9 @@ namespace ams::hactool {
}
Result Processor::ProcessAsApplicationFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, ProcessAsApplicationFileSystemCtx *ctx) {
Result Processor::ProcessAsApplicationFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, ProcessAsApplicationFileSystemContext *ctx) {
/* Ensure we have a context. */
ProcessAsApplicationFileSystemCtx local_ctx{};
ProcessAsApplicationFileSystemContext local_ctx{};
if (ctx == nullptr) {
ctx = std::addressof(local_ctx);
}
@ -86,6 +189,33 @@ namespace ams::hactool {
[&] (const fs::Path &, const fs::DirectoryEntry &) -> Result { R_SUCCEED(); },
[&] (const fs::Path &, const fs::DirectoryEntry &) -> Result { R_SUCCEED(); },
[&] (const fs::Path &path, const fs::DirectoryEntry &entry) -> Result {
/* If the path is a ticket, try to load it. */
if (PathView(entry.name).HasSuffix(TicketFileNameExtension)) {
std::shared_ptr<fs::IStorage> tik_storage;
if (const auto res = OpenFileStorage(std::addressof(tik_storage), ctx->fs, path.GetString()); R_SUCCEEDED(res)) {
/* Get ticket size. */
s64 tik_size = -1;
if (const auto res = tik_storage->GetSize(std::addressof(tik_size)); R_SUCCEEDED(res)) {
if (tik_size >= static_cast<s64>(sizeof(CommonTicketData))) {
CommonTicketData tik_data;
if (const auto res = tik_storage->Read(0, std::addressof(tik_data), sizeof(tik_data)); R_SUCCEEDED(res)) {
if (!TryLoadKeyFromCommonTicket(m_external_nca_key_manager, std::addressof(tik_data), sizeof(tik_data))) {
fprintf(stderr, "[Warning]: Failed to load common title key from ticket file (%s). Is it not a common ticket?\n", path.GetString());
}
} else {
fprintf(stderr, "[Warning]: Failed to read ticket file (%s): 2%03d-%04d\n", path.GetString(), res.GetModule(), res.GetDescription());
}
} else {
fprintf(stderr, "[Warning]: Ticket file (%s) has incorrect size: 2%03d-%04d\n", path.GetString(), res.GetModule(), res.GetDescription());
}
} else {
fprintf(stderr, "[Warning]: Failed to get size of ticket file (%s): 2%03d-%04d\n", path.GetString(), res.GetModule(), res.GetDescription());
}
} else {
fprintf(stderr, "[Warning]: Failed to open ticket file (%s): 2%03d-%04d\n", path.GetString(), res.GetModule(), res.GetDescription());
}
}
/* If the path isn't a meta nca, finish. */
R_SUCCEED_IF(!PathView(entry.name).HasSuffix(MetaNcaFileNameExtension));
@ -200,7 +330,7 @@ namespace ams::hactool {
R_SUCCEED();
}
void Processor::PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx) {
void Processor::PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx) {
auto _ = this->PrintHeader("Application File System");
{
@ -226,7 +356,7 @@ namespace ams::hactool {
AMS_UNUSED(ctx);
}
void Processor::SaveAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx) {
void Processor::SaveAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx) {
/* TODO */
AMS_UNUSED(ctx);
}

View File

@ -75,7 +75,7 @@ namespace ams::hactool {
ProcessAsNpdmContext npdm_ctx;
};
struct ProcessAsApplicationFileSystemCtx {
struct ProcessAsApplicationFileSystemContext {
std::shared_ptr<fs::fsa::IFileSystem> fs;
struct ApplicationEntryData {
@ -114,7 +114,17 @@ namespace ams::hactool {
PartitionData normal_partition;
PartitionData secure_partition;
ProcessAsApplicationFileSystemCtx app_ctx;
ProcessAsApplicationFileSystemContext app_ctx;
};
struct ProcessAsPfsContext {
std::shared_ptr<fs::IStorage> storage;
std::shared_ptr<fs::fsa::IFileSystem> fs;
bool is_exefs;
ProcessAsNpdmContext npdm_ctx;
ProcessAsApplicationFileSystemContext app_ctx;
};
private:
Options m_options;
@ -206,19 +216,22 @@ namespace ams::hactool {
Result ProcessAsNca(std::shared_ptr<fs::IStorage> storage, ProcessAsNcaContext *ctx = nullptr);
Result ProcessAsNpdm(std::shared_ptr<fs::IStorage> storage, ProcessAsNpdmContext *ctx = nullptr);
Result ProcessAsXci(std::shared_ptr<fs::IStorage> storage, ProcessAsXciContext *ctx = nullptr);
Result ProcessAsApplicationFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, ProcessAsApplicationFileSystemCtx *ctx = nullptr);
Result ProcessAsPfs(std::shared_ptr<fs::IStorage> storage, ProcessAsPfsContext *ctx = nullptr);
Result ProcessAsApplicationFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, ProcessAsApplicationFileSystemContext *ctx = nullptr);
/* Printing. */
void PrintAsNca(ProcessAsNcaContext &ctx);
void PrintAsNpdm(ProcessAsNpdmContext &ctx);
void PrintAsXci(ProcessAsXciContext &ctx);
void PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx);
void PrintAsPfs(ProcessAsPfsContext &ctx);
void PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx);
/* Saving. */
void SaveAsNca(ProcessAsNcaContext &ctx);
void SaveAsNpdm(ProcessAsNpdmContext &ctx);
void SaveAsXci(ProcessAsXciContext &ctx);
void SaveAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx);
void SaveAsPfs(ProcessAsPfsContext &ctx);
void SaveAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx);
};
inline void Processor::PrintLineImpl(const char *fmt, ...) const {

View File

@ -59,6 +59,9 @@ namespace ams::hactool {
case FileType::Xci:
R_TRY(this->ProcessAsXci(std::move(input)));
break;
case FileType::Pfs:
R_TRY(this->ProcessAsPfs(std::move(input)));
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) Atmosphère-NX
*
* 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/>.
*/
#include <stratosphere.hpp>
#include "hactool_processor.hpp"
#include "hactool_fs_utils.hpp"
namespace ams::hactool {
Result Processor::ProcessAsPfs(std::shared_ptr<fs::IStorage> storage, ProcessAsPfsContext *ctx) {
/* Ensure we have a context. */
ProcessAsPfsContext local_ctx{};
if (ctx == nullptr) {
ctx = std::addressof(local_ctx);
}
/* Set the fs. */
ctx->storage = std::move(storage);
/* Mount the partition filesystem. */
{
/* Allocate the fs. */
auto fs = fssystem::AllocateShared<fssystem::PartitionFileSystem>();
R_UNLESS(fs != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemCreatorA());
/* Initialize the filesystem. */
R_TRY(fs->Initialize(std::shared_ptr<fs::IStorage>(ctx->storage)));
/* Set the context fs. */
ctx->fs = std::move(fs);
}
/* Try to treat the context as an exefs. */
std::shared_ptr<fs::IStorage> npdm_storage;
{
bool is_exefs = false;
const auto check_npdm_res = fssystem::HasFile(std::addressof(is_exefs), ctx->fs.get(), fs::MakeConstantPath("/main.npdm"));
if (R_SUCCEEDED(check_npdm_res)) {
if (is_exefs) {
ctx->is_exefs = true;
if (const auto open_npdm_res = OpenFileStorage(std::addressof(npdm_storage), ctx->fs, "/main.npdm"); R_FAILED(open_npdm_res)) {
fprintf(stderr, "[Warning]: main.npdm exists in PartitionFileSystem but could not be opened: 2%03d-%04d\n", open_npdm_res.GetModule(), open_npdm_res.GetDescription());
}
}
} else {
fprintf(stderr, "[Warning]: Failed to check if PartitionFileSystem is exefs: 2%03d-%04d\n", check_npdm_res.GetModule(), check_npdm_res.GetDescription());
}
}
/* Parse as exefs or appfs. */
if (ctx->is_exefs) {
if (const auto process_npdm_res = this->ProcessAsNpdm(std::move(npdm_storage), std::addressof(ctx->npdm_ctx)); R_FAILED(process_npdm_res)) {
fprintf(stderr, "[Warning]: Failed to process PartitionFileSystem main.npdm: 2%03d-%04d\n", process_npdm_res.GetModule(), process_npdm_res.GetDescription());
}
} else {
if (const auto process_app_res = this->ProcessAsApplicationFileSystem(ctx->fs, std::addressof(ctx->app_ctx)); R_FAILED(process_app_res)) {
fprintf(stderr, "[Warning]: Failed to process PartitionFileSystem applications: 2%03d-%04d\n", process_app_res.GetModule(), process_app_res.GetDescription());
}
}
/* TODO: Recursive processing? */
/* Print. */
if (ctx == std::addressof(local_ctx)) {
this->PrintAsPfs(*ctx);
}
/* Save. */
if (ctx == std::addressof(local_ctx)) {
this->SaveAsPfs(*ctx);
}
R_SUCCEED();
}
void Processor::PrintAsPfs(ProcessAsPfsContext &ctx) {
if (ctx.is_exefs) {
this->PrintAsNpdm(ctx.npdm_ctx);
} else {
this->PrintAsApplicationFileSystem(ctx.app_ctx);
}
}
void Processor::SaveAsPfs(ProcessAsPfsContext &ctx) {
if (ctx.is_exefs) {
this->SaveAsNpdm(ctx.npdm_ctx);
} else {
this->SaveAsApplicationFileSystem(ctx.app_ctx);
}
}
}