content_archive: Split loading into separate functions

The constructor alone is pretty large, the reading code should be split
into its consistuent parts to make it easier to understand it without
having to build a mental model of a 300+ line function.
pull/8/head
Lioncash 6 years ago
parent 4783ad54de
commit d6604fa765

@ -102,145 +102,62 @@ bool IsValidNCA(const NCAHeader& header) {
return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
} }
u8 NCA::GetCryptoRevision() const { NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
u8 master_key_id = header.crypto_type; : file(std::move(file_)),
if (header.crypto_type_2 > master_key_id) bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
master_key_id = header.crypto_type_2; if (file == nullptr) {
if (master_key_id > 0) status = Loader::ResultStatus::ErrorNullFile;
--master_key_id; return;
return master_key_id;
}
boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
const auto master_key_id = GetCryptoRevision();
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
return boost::none;
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index),
Core::Crypto::Mode::ECB);
cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
Core::Crypto::Key128 out;
if (type == NCASectionCryptoType::XTS)
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR)
std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
else
LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
static_cast<u8>(type));
u128 out_128{};
memcpy(out_128.data(), out.data(), 16);
LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
master_key_id, header.key_index, out_128[1], out_128[0]);
return out;
} }
boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() { if (sizeof(NCAHeader) != file->ReadObject(&header)) {
const auto master_key_id = GetCryptoRevision(); LOG_ERROR(Loader, "File reader errored out during header read.");
status = Loader::ResultStatus::ErrorBadNCAHeader;
u128 rights_id{}; return;
memcpy(rights_id.data(), header.rights_id.data(), 16);
if (rights_id == u128{}) {
status = Loader::ResultStatus::ErrorInvalidRightsID;
return boost::none;
} }
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); if (!HandlePotentialHeaderDecryption()) {
if (titlekey == Core::Crypto::Key128{}) { return;
status = Loader::ResultStatus::ErrorMissingTitlekey;
return boost::none;
} }
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(),
status = Loader::ResultStatus::ErrorMissingTitlekek; [](char c) { return c == '\0'; }) != header.rights_id.end();
return boost::none;
}
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( const std::vector<NCASectionHeader> sections = ReadSectionHeaders();
keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB); is_update = std::any_of(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt); return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
});
return titlekey; if (!ReadSections(sections, bktr_base_ivfc_offset)) {
return;
} }
VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
if (!encrypted)
return in;
switch (s_header.raw.header.crypto_type) {
case NCASectionCryptoType::NONE:
LOG_DEBUG(Crypto, "called with mode=NONE");
return in;
case NCASectionCryptoType::CTR:
// During normal BKTR decryption, this entire function is skipped. This is for the metadata,
// which uses the same CTR as usual.
case NCASectionCryptoType::BKTR:
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
{
boost::optional<Core::Crypto::Key128> key = boost::none;
if (has_rights_id) {
status = Loader::ResultStatus::Success; status = Loader::ResultStatus::Success;
key = GetTitlekey();
if (key == boost::none) {
if (status == Loader::ResultStatus::Success)
status = Loader::ResultStatus::ErrorMissingTitlekey;
return nullptr;
}
} else {
key = GetKeyAreaKey(NCASectionCryptoType::CTR);
if (key == boost::none) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return nullptr;
}
} }
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>( NCA::~NCA() = default;
std::move(in), key.value(), starting_offset);
std::vector<u8> iv(16);
for (u8 i = 0; i < 8; ++i)
iv[i] = s_header.raw.section_ctr[0x8 - i - 1];
out->SetIV(iv);
return std::static_pointer_cast<VfsFile>(out);
}
case NCASectionCryptoType::XTS:
// TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
default:
LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
static_cast<u8>(s_header.raw.header.crypto_type));
return nullptr;
}
}
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
: file(std::move(file_)),
bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
status = Loader::ResultStatus::Success;
if (file == nullptr) { bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) {
status = Loader::ResultStatus::ErrorNullFile; if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
return; status = Loader::ResultStatus::ErrorNCA2;
return false;
} }
if (sizeof(NCAHeader) != file->ReadObject(&header)) { if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
LOG_ERROR(Loader, "File reader errored out during header read."); status = Loader::ResultStatus::ErrorNCA0;
status = Loader::ResultStatus::ErrorBadNCAHeader; return false;
return;
} }
encrypted = false; return true;
}
if (!IsValidNCA(header)) { bool NCA::HandlePotentialHeaderDecryption() {
if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { if (IsValidNCA(header)) {
status = Loader::ResultStatus::ErrorNCA2; return true;
return;
} }
if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
status = Loader::ResultStatus::ErrorNCA0; if (!CheckSupportedNCA(header)) {
return; return false;
} }
NCAHeader dec_header{}; NCAHeader dec_header{};
@ -252,26 +169,22 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off
header = dec_header; header = dec_header;
encrypted = true; encrypted = true;
} else { } else {
if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { if (!CheckSupportedNCA(dec_header)) {
status = Loader::ResultStatus::ErrorNCA2; return false;
return;
}
if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
status = Loader::ResultStatus::ErrorNCA0;
return;
} }
if (!keys.HasKey(Core::Crypto::S256KeyType::Header)) if (keys.HasKey(Core::Crypto::S256KeyType::Header)) {
status = Loader::ResultStatus::ErrorMissingHeaderKey;
else
status = Loader::ResultStatus::ErrorIncorrectHeaderKey; status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
return; } else {
status = Loader::ResultStatus::ErrorMissingHeaderKey;
} }
return false;
} }
has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(), return true;
[](char c) { return c == '\0'; }) != header.rights_id.end(); }
std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const {
const std::ptrdiff_t number_sections = const std::ptrdiff_t number_sections =
std::count_if(std::begin(header.section_tables), std::end(header.section_tables), std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
[](NCASectionTableEntry entry) { return entry.media_offset > 0; }); [](NCASectionTableEntry entry) { return entry.media_offset > 0; });
@ -289,17 +202,30 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
} }
is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) { return sections;
return header.raw.header.crypto_type == NCASectionCryptoType::BKTR; }
}) != sections.end();
ivfc_offset = 0;
for (std::ptrdiff_t i = 0; i < number_sections; ++i) { bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) {
for (std::size_t i = 0; i < sections.size(); ++i) {
const auto& section = sections[i]; const auto& section = sections[i];
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
const std::size_t base_offset = if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER; return false;
}
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
if (!ReadPFS0Section(section, header.section_tables[i])) {
return false;
}
}
}
return true;
}
bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
u64 bktr_base_ivfc_offset) {
const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER;
ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
const std::size_t romfs_offset = base_offset + ivfc_offset; const std::size_t romfs_offset = base_offset + ivfc_offset;
const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
@ -308,33 +234,31 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off
if (dec == nullptr) { if (dec == nullptr) {
if (status != Loader::ResultStatus::Success) if (status != Loader::ResultStatus::Success)
return; return false;
if (has_rights_id) if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return; return false;
} }
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
status = Loader::ResultStatus::ErrorBadBKTRHeader; status = Loader::ResultStatus::ErrorBadBKTRHeader;
return; return false;
} }
if (section.bktr.relocation.offset + section.bktr.relocation.size != if (section.bktr.relocation.offset + section.bktr.relocation.size !=
section.bktr.subsection.offset) { section.bktr.subsection.offset) {
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
return; return false;
} }
const u64 size = const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
header.section_tables[i].media_offset);
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
return; return false;
} }
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
@ -342,37 +266,33 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) != if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
sizeof(RelocationBlock)) { sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadRelocationBlock; status = Loader::ResultStatus::ErrorBadRelocationBlock;
return; return false;
} }
SubsectionBlock subsection_block{}; SubsectionBlock subsection_block{};
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
sizeof(RelocationBlock)) { sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadSubsectionBlock; status = Loader::ResultStatus::ErrorBadSubsectionBlock;
return; return false;
} }
std::vector<RelocationBucketRaw> relocation_buckets_raw( std::vector<RelocationBucketRaw> relocation_buckets_raw(
(section.bktr.relocation.size - sizeof(RelocationBlock)) / (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw));
sizeof(RelocationBucketRaw));
if (dec->ReadBytes(relocation_buckets_raw.data(), if (dec->ReadBytes(relocation_buckets_raw.data(),
section.bktr.relocation.size - sizeof(RelocationBlock), section.bktr.relocation.size - sizeof(RelocationBlock),
section.bktr.relocation.offset + sizeof(RelocationBlock) - section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) !=
offset) !=
section.bktr.relocation.size - sizeof(RelocationBlock)) { section.bktr.relocation.size - sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadRelocationBuckets; status = Loader::ResultStatus::ErrorBadRelocationBuckets;
return; return false;
} }
std::vector<SubsectionBucketRaw> subsection_buckets_raw( std::vector<SubsectionBucketRaw> subsection_buckets_raw(
(section.bktr.subsection.size - sizeof(SubsectionBlock)) / (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw));
sizeof(SubsectionBucketRaw));
if (dec->ReadBytes(subsection_buckets_raw.data(), if (dec->ReadBytes(subsection_buckets_raw.data(),
section.bktr.subsection.size - sizeof(SubsectionBlock), section.bktr.subsection.size - sizeof(SubsectionBlock),
section.bktr.subsection.offset + sizeof(SubsectionBlock) - section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) !=
offset) !=
section.bktr.subsection.size - sizeof(SubsectionBlock)) { section.bktr.subsection.size - sizeof(SubsectionBlock)) {
status = Loader::ResultStatus::ErrorBadSubsectionBuckets; status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
return; return false;
} }
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
@ -384,8 +304,7 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off
u32 ctr_low; u32 ctr_low;
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low)); std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
subsection_buckets.back().entries.push_back( subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
{section.bktr.relocation.offset, {0}, ctr_low});
subsection_buckets.back().entries.push_back({size, {0}, 0}); subsection_buckets.back().entries.push_back({size, {0}, 0});
boost::optional<Core::Crypto::Key128> key = boost::none; boost::optional<Core::Crypto::Key128> key = boost::none;
@ -395,45 +314,45 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off
key = GetTitlekey(); key = GetTitlekey();
if (key == boost::none) { if (key == boost::none) {
status = Loader::ResultStatus::ErrorMissingTitlekey; status = Loader::ResultStatus::ErrorMissingTitlekey;
return; return false;
} }
} else { } else {
key = GetKeyAreaKey(NCASectionCryptoType::BKTR); key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
if (key == boost::none) { if (key == boost::none) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey; status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return; return false;
} }
} }
} }
if (bktr_base_romfs == nullptr) { if (bktr_base_romfs == nullptr) {
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
return; return false;
} }
auto bktr = std::make_shared<BKTR>( auto bktr = std::make_shared<BKTR>(
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset), bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
relocation_block, relocation_buckets, subsection_block, subsection_buckets, relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
encrypted, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
bktr_base_ivfc_offset, section.raw.section_ctr); section.raw.section_ctr);
// BKTR applies to entire IVFC, so make an offset version to level 6 // BKTR applies to entire IVFC, so make an offset version to level 6
files.push_back(std::make_shared<OffsetVfsFile>( files.push_back(std::make_shared<OffsetVfsFile>(
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset)); bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
romfs = files.back();
} else { } else {
files.push_back(std::move(dec)); files.push_back(std::move(dec));
}
romfs = files.back(); romfs = files.back();
return true;
} }
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) {
MEDIA_OFFSET_MULTIPLIER) + const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) +
section.pfs0.pfs0_header_offset; section.pfs0.pfs0_header_offset;
u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
header.section_tables[i].media_offset);
auto dec = auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
if (dec != nullptr) { if (dec != nullptr) {
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
@ -446,24 +365,133 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return; return false;
} }
} else { } else {
if (status != Loader::ResultStatus::Success) if (status != Loader::ResultStatus::Success)
return; return false;
if (has_rights_id) if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return; return false;
}
return true;
}
u8 NCA::GetCryptoRevision() const {
u8 master_key_id = header.crypto_type;
if (header.crypto_type_2 > master_key_id)
master_key_id = header.crypto_type_2;
if (master_key_id > 0)
--master_key_id;
return master_key_id;
}
boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
const auto master_key_id = GetCryptoRevision();
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
return boost::none;
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index),
Core::Crypto::Mode::ECB);
cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
Core::Crypto::Key128 out;
if (type == NCASectionCryptoType::XTS)
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR)
std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
else
LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
static_cast<u8>(type));
u128 out_128{};
memcpy(out_128.data(), out.data(), 16);
LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
master_key_id, header.key_index, out_128[1], out_128[0]);
return out;
} }
boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
const auto master_key_id = GetCryptoRevision();
u128 rights_id{};
memcpy(rights_id.data(), header.rights_id.data(), 16);
if (rights_id == u128{}) {
status = Loader::ResultStatus::ErrorInvalidRightsID;
return boost::none;
} }
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
if (titlekey == Core::Crypto::Key128{}) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
return boost::none;
}
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
status = Loader::ResultStatus::ErrorMissingTitlekek;
return boost::none;
} }
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB);
cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt);
return titlekey;
}
VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
if (!encrypted)
return in;
switch (s_header.raw.header.crypto_type) {
case NCASectionCryptoType::NONE:
LOG_DEBUG(Crypto, "called with mode=NONE");
return in;
case NCASectionCryptoType::CTR:
// During normal BKTR decryption, this entire function is skipped. This is for the metadata,
// which uses the same CTR as usual.
case NCASectionCryptoType::BKTR:
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
{
boost::optional<Core::Crypto::Key128> key = boost::none;
if (has_rights_id) {
status = Loader::ResultStatus::Success; status = Loader::ResultStatus::Success;
key = GetTitlekey();
if (key == boost::none) {
if (status == Loader::ResultStatus::Success)
status = Loader::ResultStatus::ErrorMissingTitlekey;
return nullptr;
}
} else {
key = GetKeyAreaKey(NCASectionCryptoType::CTR);
if (key == boost::none) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return nullptr;
}
} }
NCA::~NCA() = default; auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
std::move(in), key.value(), starting_offset);
std::vector<u8> iv(16);
for (u8 i = 0; i < 8; ++i)
iv[i] = s_header.raw.section_ctr[0x8 - i - 1];
out->SetIV(iv);
return std::static_pointer_cast<VfsFile>(out);
}
case NCASectionCryptoType::XTS:
// TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
default:
LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
static_cast<u8>(s_header.raw.header.crypto_type));
return nullptr;
}
}
Loader::ResultStatus NCA::GetStatus() const { Loader::ResultStatus NCA::GetStatus() const {
return status; return status;

@ -106,6 +106,15 @@ protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private: private:
bool CheckSupportedNCA(const NCAHeader& header);
bool HandlePotentialHeaderDecryption();
std::vector<NCASectionHeader> ReadSectionHeaders() const;
bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset);
bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
u64 bktr_base_ivfc_offset);
bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
u8 GetCryptoRevision() const; u8 GetCryptoRevision() const;
boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
boost::optional<Core::Crypto::Key128> GetTitlekey(); boost::optional<Core::Crypto::Key128> GetTitlekey();
@ -118,15 +127,15 @@ private:
VirtualDir exefs = nullptr; VirtualDir exefs = nullptr;
VirtualFile file; VirtualFile file;
VirtualFile bktr_base_romfs; VirtualFile bktr_base_romfs;
u64 ivfc_offset; u64 ivfc_offset = 0;
NCAHeader header{}; NCAHeader header{};
bool has_rights_id{}; bool has_rights_id{};
Loader::ResultStatus status{}; Loader::ResultStatus status{};
bool encrypted; bool encrypted = false;
bool is_update; bool is_update = false;
Core::Crypto::KeyManager keys; Core::Crypto::KeyManager keys;
}; };

Loading…
Cancel
Save