diff options
author | Zach Hilman <zachhilman@gmail.com> | 2018-07-28 05:55:23 +0200 |
---|---|---|
committer | Zach Hilman <zachhilman@gmail.com> | 2018-08-01 06:16:54 +0200 |
commit | df5b75694f5abde94ccf05fa6c7a557b1ba9079b (patch) | |
tree | 70f0cf96b1a9834360fb1c5d5547939693ecd577 /src/core/file_sys/content_archive.cpp | |
parent | Merge pull request #871 from bunnei/audio-config (diff) | |
download | yuzu-df5b75694f5abde94ccf05fa6c7a557b1ba9079b.tar yuzu-df5b75694f5abde94ccf05fa6c7a557b1ba9079b.tar.gz yuzu-df5b75694f5abde94ccf05fa6c7a557b1ba9079b.tar.bz2 yuzu-df5b75694f5abde94ccf05fa6c7a557b1ba9079b.tar.lz yuzu-df5b75694f5abde94ccf05fa6c7a557b1ba9079b.tar.xz yuzu-df5b75694f5abde94ccf05fa6c7a557b1ba9079b.tar.zst yuzu-df5b75694f5abde94ccf05fa6c7a557b1ba9079b.zip |
Diffstat (limited to 'src/core/file_sys/content_archive.cpp')
-rw-r--r-- | src/core/file_sys/content_archive.cpp | 168 |
1 files changed, 138 insertions, 30 deletions
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 61cb0bbe3..add01974e 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -6,9 +6,12 @@ #include <utility> #include "common/logging/log.h" +#include "core/crypto/aes_util.h" +#include "core/crypto/ctr_encryption_layer.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/vfs_offset.h" #include "core/loader/loader.h" +#include "mbedtls/cipher.h" #include "romfs.h" namespace FileSys { @@ -29,11 +32,19 @@ enum class NCASectionFilesystemType : u8 { struct NCASectionHeaderBlock { INSERT_PADDING_BYTES(3); NCASectionFilesystemType filesystem_type; - u8 crypto_type; + NCASectionCryptoType crypto_type; INSERT_PADDING_BYTES(3); }; static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); +struct NCASectionRaw { + NCASectionHeaderBlock header; + std::array<u8, 0x138> block_data; + std::array<u8, 0x8> section_ctr; + INSERT_PADDING_BYTES(0xB8); +}; +static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); + struct PFS0Superblock { NCASectionHeaderBlock header_block; std::array<u8, 0x20> hash; @@ -43,62 +54,155 @@ struct PFS0Superblock { u64_le hash_table_size; u64_le pfs0_header_offset; u64_le pfs0_size; - INSERT_PADDING_BYTES(432); + INSERT_PADDING_BYTES(0x1B0); }; static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); struct RomFSSuperblock { NCASectionHeaderBlock header_block; IVFCHeader ivfc; + INSERT_PADDING_BYTES(0x118); +}; +static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); + +union NCASectionHeader { + NCASectionRaw raw; + PFS0Superblock pfs0; + RomFSSuperblock romfs; }; -static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size."); +static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); + +bool IsValidNCA(const NCAHeader& header) { + // TODO(DarkLordZach): Add NCA2/NCA0 support. + return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); +} + +Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) { + 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; + + std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); + if (!Crypto::keys.ValidateKey(Crypto::S128KeyType::KEY_AREA, master_key_id, header.key_index)) { + status = Loader::ResultStatus::ErrorEncrypted; + return {}; + } + Crypto::AESCipher<Crypto::Key128> cipher( + Crypto::keys.GetKey(Crypto::S128KeyType::KEY_AREA, master_key_id, header.key_index), + Crypto::Mode::ECB); + cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Crypto::Op::DECRYPT); + + Crypto::Key128 out; + if (type == NCASectionCryptoType::XTS) + std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); + else if (type == NCASectionCryptoType::CTR) + 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 = *reinterpret_cast<u128*>(&out); + LOG_DEBUG(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; +} + +VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) { + if (!encrypted) + return in; + + switch (header.raw.header.crypto_type) { + case NCASectionCryptoType::NONE: + LOG_DEBUG(Crypto, "called with mode=NONE"); + return in; + case NCASectionCryptoType::CTR: + LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); + { + auto out = std::make_shared<Crypto::CTREncryptionLayer>( + std::move(in), GetKeyAreaKey(NCASectionCryptoType::CTR), starting_offset); + std::vector<u8> iv(16, 0); + for (u8 i = 0; i < 8; ++i) + iv[i] = header.raw.section_ctr[0x8 - i - 1]; + out->SetIV(iv); + return out; + } + case NCASectionCryptoType::XTS: + // TODO(DarkLordZach): Implement XTSEncryptionLayer and title key encryption. + default: + LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", + static_cast<u8>(header.raw.header.crypto_type)); + return nullptr; + } +} NCA::NCA(VirtualFile file_) : file(std::move(file_)) { if (sizeof(NCAHeader) != file->ReadObject(&header)) - LOG_CRITICAL(Loader, "File reader errored out during header read."); + LOG_ERROR(Loader, "File reader errored out during header read."); + + encrypted = false; if (!IsValidNCA(header)) { - status = Loader::ResultStatus::ErrorInvalidFormat; - return; + NCAHeader dec_header{}; + if (!Crypto::keys.ValidateKey(Crypto::S256KeyType::HEADER)) { + status = Loader::ResultStatus::ErrorEncrypted; + return; + } + Crypto::AESCipher<Crypto::Key256> cipher(Crypto::keys.GetKey(Crypto::S256KeyType::HEADER), + Crypto::Mode::XTS); + cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, Crypto::Op::DECRYPT); + if (IsValidNCA(dec_header)) { + header = dec_header; + encrypted = true; + } else { + status = Loader::ResultStatus::ErrorInvalidFormat; + return; + } } - std::ptrdiff_t number_sections = + const std::ptrdiff_t number_sections = std::count_if(std::begin(header.section_tables), std::end(header.section_tables), [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); + std::vector<NCASectionHeader> sections(number_sections); + const auto length_sections = SECTION_HEADER_SIZE * number_sections; + + if (encrypted) { + auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); + if (!Crypto::keys.ValidateKey(Crypto::S256KeyType::HEADER)) { + status = Loader::ResultStatus::ErrorEncrypted; + return; + } + Crypto::AESCipher<Crypto::Key256> cipher(Crypto::keys.GetKey(Crypto::S256KeyType::HEADER), + Crypto::Mode::XTS); + cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, + Crypto::Op::DECRYPT); + } else { + file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); + } + for (std::ptrdiff_t i = 0; i < number_sections; ++i) { - // Seek to beginning of this section. - NCASectionHeaderBlock block{}; - if (sizeof(NCASectionHeaderBlock) != - file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) - LOG_CRITICAL(Loader, "File reader errored out during header read."); - - if (block.filesystem_type == NCASectionFilesystemType::ROMFS) { - RomFSSuperblock sb{}; - if (sizeof(RomFSSuperblock) != - file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) - LOG_CRITICAL(Loader, "File reader errored out during header read."); + auto section = sections[i]; + if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { const size_t romfs_offset = header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + - sb.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; - const size_t romfs_size = sb.ivfc.levels[IVFC_MAX_LEVEL - 1].size; - files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset)); + section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; + const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; + files.emplace_back( + Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset), + romfs_offset)); romfs = files.back(); - } else if (block.filesystem_type == NCASectionFilesystemType::PFS0) { - PFS0Superblock sb{}; - // Seek back to beginning of this section. - if (sizeof(PFS0Superblock) != - file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) - LOG_CRITICAL(Loader, "File reader errored out during header read."); - + } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * MEDIA_OFFSET_MULTIPLIER) + - sb.pfs0_header_offset; + section.pfs0.pfs0_header_offset; u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - header.section_tables[i].media_offset); auto npfs = std::make_shared<PartitionFilesystem>( - std::make_shared<OffsetVfsFile>(file, size, offset)); + Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset)); if (npfs->GetStatus() == Loader::ResultStatus::Success) { dirs.emplace_back(npfs); @@ -153,6 +257,10 @@ VirtualDir NCA::GetExeFS() const { return exefs; } +VirtualFile NCA::GetBaseFile() const { + return file; +} + bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { return false; } |