From a9dc5a3c1058d22873a201c08bd6d095405789ae Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 16:57:00 -0400 Subject: [PATCH 01/24] xci: Fix error masking issue Prevents NCA-related errors from being masked into MissingProgramNCA or MissingKeyFile --- src/core/file_sys/card_image.cpp | 9 +++++++++ src/core/file_sys/card_image.h | 2 ++ src/core/loader/xci.cpp | 11 ++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 1d7c7fb10..508f09e56 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -43,6 +43,8 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { partitions[static_cast(partition)] = std::make_shared(raw); } + program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA; + auto result = AddNCAFromPartition(XCIPartition::Secure); if (result != Loader::ResultStatus::Success) { status = result; @@ -76,6 +78,10 @@ Loader::ResultStatus XCI::GetStatus() const { return status; } +Loader::ResultStatus XCI::GetProgramNCAStatus() const { + return program_nca_status; +} + VirtualDir XCI::GetPartition(XCIPartition partition) const { return partitions[static_cast(partition)]; } @@ -143,6 +149,9 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { if (file->GetExtension() != "nca") continue; auto nca = std::make_shared(file); + if (nca->GetType() == NCAContentType::Program) { + program_nca_status = nca->GetStatus(); + } if (nca->GetStatus() == Loader::ResultStatus::Success) { ncas.push_back(std::move(nca)); } else { diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index a03d5264e..54ab828d1 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -59,6 +59,7 @@ public: explicit XCI(VirtualFile file); Loader::ResultStatus GetStatus() const; + Loader::ResultStatus GetProgramNCAStatus() const; u8 GetFormatVersion() const; @@ -90,6 +91,7 @@ private: GamecardHeader header{}; Loader::ResultStatus status; + Loader::ResultStatus program_nca_status; std::vector partitions; std::vector> ncas; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 4c4979545..9dc4d1f35 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -61,11 +61,12 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr& process) { if (xci->GetStatus() != ResultStatus::Success) return xci->GetStatus(); - if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) { - if (!Core::Crypto::KeyManager::KeyFileExists(false)) - return ResultStatus::ErrorMissingProductionKeyFile; - return ResultStatus::ErrorXCIMissingProgramNCA; - } + if (xci->GetProgramNCAStatus() != ResultStatus::Success) + return xci->GetProgramNCAStatus(); + + const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program); + if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false)) + return ResultStatus::ErrorMissingProductionKeyFile; auto result = nca_loader->Load(process); if (result != ResultStatus::Success) From 6dd369ab88efa9f183104cf26304afc940b462cf Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 16:58:49 -0400 Subject: [PATCH 02/24] ctr_encryption_layer: Fix bug when transcoding small data Fixes a bug where data lengths of less than size 0x10 will fail or have misleading return values. --- src/core/crypto/ctr_encryption_layer.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp index 106db02b3..3ea60dbd0 100644 --- a/src/core/crypto/ctr_encryption_layer.cpp +++ b/src/core/crypto/ctr_encryption_layer.cpp @@ -20,10 +20,8 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { if (sector_offset == 0) { UpdateIV(base_offset + offset); std::vector raw = base->ReadBytes(length, offset); - if (raw.size() != length) - return Read(data, raw.size(), offset); - cipher.Transcode(raw.data(), length, data, Op::Decrypt); - return length; + cipher.Transcode(raw.data(), raw.size(), data, Op::Decrypt); + return raw.size(); } // offset does not fall on block boundary (0x10) @@ -34,7 +32,7 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { if (length + sector_offset < 0x10) { std::memcpy(data, block.data() + sector_offset, std::min(length, read)); - return read; + return std::min(length, read); } std::memcpy(data, block.data() + sector_offset, read); return read + Read(data + read, length - read, offset + read); From 10e5356e9ac5756d8a48498cd1270862d3013476 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:00:35 -0400 Subject: [PATCH 03/24] aes_util: Make XTSTranscode stricter about sizes XTS with Nintendo Tweak will fail mysteriously if the sector size is not 0x4000. Upgrade the critical log to an assert to prevent undefined behavior. --- src/core/crypto/aes_util.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp index a9876c83e..72e4bed67 100644 --- a/src/core/crypto/aes_util.cpp +++ b/src/core/crypto/aes_util.cpp @@ -99,10 +99,7 @@ void AESCipher::Transcode(const u8* src, size_t size, u8* dest, Op template void AESCipher::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size, Op op) { - if (size % sector_size > 0) { - LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size."); - return; - } + ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size."); for (size_t i = 0; i < size; i += sector_size) { SetIV(CalculateNintendoTweak(sector_id++)); @@ -112,4 +109,4 @@ void AESCipher::XTSTranscode(const u8* src, size_t size, u8* dest, template class AESCipher; template class AESCipher; -} // namespace Core::Crypto \ No newline at end of file +} // namespace Core::Crypto From c4845df3d4b9fc3fc19dd936af87090ffb3fbdf2 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:01:32 -0400 Subject: [PATCH 04/24] xts_encryption_layer: Implement XTSEncryptionLayer --- src/core/crypto/xts_encryption_layer.cpp | 54 ++++++++++++++++++++++++ src/core/crypto/xts_encryption_layer.h | 26 ++++++++++++ src/core/file_sys/content_archive.cpp | 2 +- 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/core/crypto/xts_encryption_layer.cpp create mode 100644 src/core/crypto/xts_encryption_layer.h diff --git a/src/core/crypto/xts_encryption_layer.cpp b/src/core/crypto/xts_encryption_layer.cpp new file mode 100644 index 000000000..431099580 --- /dev/null +++ b/src/core/crypto/xts_encryption_layer.cpp @@ -0,0 +1,54 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/assert.h" +#include "core/crypto/xts_encryption_layer.h" + +namespace Core::Crypto { + +XTSEncryptionLayer::XTSEncryptionLayer(FileSys::VirtualFile base_, Key256 key_) + : EncryptionLayer(std::move(base_)), cipher(key_, Mode::XTS) {} + +size_t XTSEncryptionLayer::Read(u8* data, size_t length, size_t offset) const { + if (length == 0) + return 0; + + const auto sector_offset = offset & 0x3FFF; + if (sector_offset == 0) { + if (length % 0x4000 == 0) { + std::vector raw = base->ReadBytes(length, offset); + cipher.XTSTranscode(raw.data(), raw.size(), data, offset / 0x4000, 0x4000, Op::Decrypt); + return raw.size(); + } + if (length > 0x4000) { + const auto rem = length % 0x4000; + const auto read = length - rem; + return Read(data, read, offset) + Read(data + read, rem, offset + read); + } + std::vector buffer = base->ReadBytes(0x4000, offset); + if (buffer.size() < 0x4000) + buffer.resize(0x4000); + cipher.XTSTranscode(buffer.data(), buffer.size(), buffer.data(), offset / 0x4000, 0x4000, + Op::Decrypt); + std::memcpy(data, buffer.data(), std::min(buffer.size(), length)); + return std::min(buffer.size(), length); + } + + // offset does not fall on block boundary (0x4000) + std::vector block = base->ReadBytes(0x4000, offset - sector_offset); + if (block.size() < 0x4000) + block.resize(0x4000); + cipher.XTSTranscode(block.data(), block.size(), block.data(), (offset - sector_offset) / 0x4000, + 0x4000, Op::Decrypt); + const size_t read = 0x4000 - sector_offset; + + if (length + sector_offset < 0x4000) { + std::memcpy(data, block.data() + sector_offset, std::min(length, read)); + return std::min(length, read); + } + std::memcpy(data, block.data() + sector_offset, read); + return read + Read(data + read, length - read, offset + read); +} +} // namespace Core::Crypto diff --git a/src/core/crypto/xts_encryption_layer.h b/src/core/crypto/xts_encryption_layer.h new file mode 100644 index 000000000..1e1acaf4a --- /dev/null +++ b/src/core/crypto/xts_encryption_layer.h @@ -0,0 +1,26 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "core/crypto/aes_util.h" +#include "core/crypto/encryption_layer.h" +#include "core/crypto/key_manager.h" + +namespace Core::Crypto { + +// Sits on top of a VirtualFile and provides XTS-mode AES decription. +class XTSEncryptionLayer : public EncryptionLayer { +public: + XTSEncryptionLayer(FileSys::VirtualFile base, Key256 key); + + size_t Read(u8* data, size_t length, size_t offset) const override; + +private: + // Must be mutable as operations modify cipher contexts. + mutable AESCipher cipher; +}; + +} // namespace Core::Crypto diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 47afcad9b..008e11d8c 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -178,7 +178,7 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting return std::static_pointer_cast(out); } case NCASectionCryptoType::XTS: - // TODO(DarkLordZach): Implement XTSEncryptionLayer. + // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs default: LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", static_cast(s_header.raw.header.crypto_type)); From 2164702cf7f878c84a1f148daca2416911e6e939 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:02:31 -0400 Subject: [PATCH 05/24] nax: Add AppLoader_NAX and update loader to support it --- src/core/loader/loader.cpp | 14 ++++++-- src/core/loader/loader.h | 1 + src/core/loader/nax.cpp | 65 ++++++++++++++++++++++++++++++++++++++ src/core/loader/nax.h | 43 +++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 src/core/loader/nax.cpp create mode 100644 src/core/loader/nax.h diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 70ef5d240..fe5d71f68 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -11,6 +11,7 @@ #include "core/hle/kernel/process.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/elf.h" +#include "core/loader/nax.h" #include "core/loader/nca.h" #include "core/loader/nro.h" #include "core/loader/nso.h" @@ -32,6 +33,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) { CHECK_TYPE(NRO) CHECK_TYPE(NCA) CHECK_TYPE(XCI) + CHECK_TYPE(NAX) #undef CHECK_TYPE @@ -73,6 +75,8 @@ std::string GetFileTypeString(FileType type) { return "NCA"; case FileType::XCI: return "XCI"; + case FileType::NAX: + return "NAX"; case FileType::DeconstructedRomDirectory: return "Directory"; case FileType::Error: @@ -150,13 +154,18 @@ static std::unique_ptr GetFileLoader(FileSys::VirtualFile file, FileT case FileType::NRO: return std::make_unique(std::move(file)); - // NX NCA file format. + // NX NCA (Nintendo Content Archive) file format. case FileType::NCA: return std::make_unique(std::move(file)); + // NX XCI (nX Card Image) file format. case FileType::XCI: return std::make_unique(std::move(file)); + // NX NAX (NintendoAesXts) file format. + case FileType::NAX: + return std::make_unique(std::move(file)); + // NX deconstructed ROM directory. case FileType::DeconstructedRomDirectory: return std::make_unique(std::move(file)); @@ -170,7 +179,8 @@ std::unique_ptr GetLoader(FileSys::VirtualFile file) { FileType type = IdentifyFile(file); FileType filename_type = GuessFromFilename(file->GetName()); - if (type != filename_type) { + // Special case: 00 is either a NCA or NAX. + if (type != filename_type && !(file->GetName() == "00" && type == FileType::NAX)) { LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName()); if (FileType::Unknown == type) type = filename_type; diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index b74cfbf8a..d132fb4e8 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -32,6 +32,7 @@ enum class FileType { NRO, NCA, XCI, + NAX, DeconstructedRomDirectory, }; diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp new file mode 100644 index 000000000..76390bf46 --- /dev/null +++ b/src/core/loader/nax.cpp @@ -0,0 +1,65 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/core.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/romfs.h" +#include "core/hle/kernel/process.h" +#include "core/loader/nax.h" + +namespace Loader { + +AppLoader_NAX::AppLoader_NAX(FileSys::VirtualFile file) + : AppLoader(file), nax(std::make_unique(file)), + nca_loader(std::make_unique(nax->GetDecrypted())) {} + +AppLoader_NAX::~AppLoader_NAX() = default; + +FileType AppLoader_NAX::IdentifyType(const FileSys::VirtualFile& file) { + FileSys::NAX nax(file); + + if (nax.GetStatus() == ResultStatus::Success && nax.AsNCA() != nullptr && + nax.AsNCA()->GetStatus() == ResultStatus::Success) { + return FileType::NAX; + } + + return FileType::Error; +} + +ResultStatus AppLoader_NAX::Load(Kernel::SharedPtr& process) { + if (is_loaded) { + return ResultStatus::ErrorAlreadyLoaded; + } + + if (nax->GetStatus() != ResultStatus::Success) + return nax->GetStatus(); + + const auto nca = nax->AsNCA(); + if (nca == nullptr) { + if (!Core::Crypto::KeyManager::KeyFileExists(false)) + return ResultStatus::ErrorMissingProductionKeyFile; + return ResultStatus::ErrorNAXInconvertibleToNCA; + } + + if (nca->GetStatus() != ResultStatus::Success) + return nca->GetStatus(); + + const auto result = nca_loader->Load(process); + if (result != ResultStatus::Success) + return result; + + is_loaded = true; + + return ResultStatus::Success; +} + +ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) { + return nca_loader->ReadRomFS(dir); +} + +ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { + return nca_loader->ReadProgramId(out_program_id); +} +} // namespace Loader diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h new file mode 100644 index 000000000..08d6ef346 --- /dev/null +++ b/src/core/loader/nax.h @@ -0,0 +1,43 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" +#include "core/loader/nca.h" + +namespace Loader { + +/// Loads a NAX file +class AppLoader_NAX final : public AppLoader { +public: + explicit AppLoader_NAX(FileSys::VirtualFile file); + ~AppLoader_NAX(); + + /** + * Returns the type of the file + * @param file std::shared_ptr open file + * @return FileType found, or FileType::Error if this loader doesn't know it + */ + static FileType IdentifyType(const FileSys::VirtualFile& file); + + FileType GetFileType() override { + return IdentifyType(file); + } + + ResultStatus Load(Kernel::SharedPtr& process) override; + + ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; + ResultStatus ReadProgramId(u64& out_program_id) override; + +private: + std::unique_ptr nax; + std::unique_ptr nca_loader; +}; + +} // namespace Loader From b247e0cab0e01476d8bf56e989d34483782b7cae Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:02:50 -0400 Subject: [PATCH 06/24] loader: Add new NAX-specific errors and messages --- src/core/loader/loader.cpp | 15 ++++++++++++++- src/core/loader/loader.h | 13 +++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index fe5d71f68..c13fb49b8 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -87,7 +87,7 @@ std::string GetFileTypeString(FileType type) { return "unknown"; } -constexpr std::array RESULT_MESSAGES{ +constexpr std::array RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -124,6 +124,19 @@ constexpr std::array RESULT_MESSAGES{ "There was a general error loading the NRO into emulated memory.", "There is no icon available.", "There is no control data available.", + "The NAX file has a bad header.", + "The NAX file has incorrect size as determined by the header.", + "The HMAC to generated the NAX decryption keys failed.", + "The HMAC to validate the NAX decryption keys failed.", + "The NAX key derivation failed.", + "The NAX file cannot be interpreted as an NCA file.", + "The NAX file has an incorrect path.", + "The SD seed could not be found or derived.", + "The SD KEK Source could not be found.", + "The AES KEK Generation Source could not be found.", + "The AES Key Generation Source could not be found.", + "The SD Save Key Source could not be found.", + "The SD NCA Key Source could not be found.", }; std::ostream& operator<<(std::ostream& os, ResultStatus status) { diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index d132fb4e8..885fee84c 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -94,6 +94,19 @@ enum class ResultStatus : u16 { ErrorLoadingNRO, ErrorNoIcon, ErrorNoControl, + ErrorBadNAXHeader, + ErrorIncorrectNAXFileSize, + ErrorNAXKeyHMACFailed, + ErrorNAXValidationHMACFailed, + ErrorNAXKeyDerivationFailed, + ErrorNAXInconvertibleToNCA, + ErrorBadNAXFilePath, + ErrorMissingSDSeed, + ErrorMissingSDKEKSource, + ErrorMissingAESKEKGenerationSource, + ErrorMissingAESKeyGenerationSource, + ErrorMissingSDSaveKeySource, + ErrorMissingSDNCAKeySource, }; std::ostream& operator<<(std::ostream& os, ResultStatus status); From 410062031b0c29d63abb21e1b9ce70b4b700ae8f Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:03:31 -0400 Subject: [PATCH 07/24] filesystem: Add logging to registration getters --- .../hle/service/filesystem/filesystem.cpp | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 6f9c64263..1a318b233 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -305,17 +305,38 @@ ResultVal OpenSDMC() { } std::shared_ptr GetSystemNANDContents() { + LOG_TRACE(Service_FS, "Opening System NAND Contents"); + + if (bis_factory == nullptr) + return nullptr; + return bis_factory->GetSystemNANDContents(); } std::shared_ptr GetUserNANDContents() { + LOG_TRACE(Service_FS, "Opening User NAND Contents"); + + if (bis_factory == nullptr) + return nullptr; + return bis_factory->GetUserNANDContents(); } -void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) { - romfs_factory = nullptr; - save_data_factory = nullptr; - sdmc_factory = nullptr; +std::shared_ptr GetSDMCContents() { + LOG_TRACE(Service_FS, "Opening SDMC Contents"); + + if (sdmc_factory == nullptr) + return nullptr; + + return sdmc_factory->GetSDMCContents(); +} + +void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { + if (overwrite) { + bis_factory = nullptr; + save_data_factory = nullptr; + sdmc_factory = nullptr; + } auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite); From ef3768f323fc06eb41c603fe797a26412424acd4 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:04:38 -0400 Subject: [PATCH 08/24] filesystem: Add CreateFactories methods to fs Allows frontend to create registration caches for use before a game has booted. --- src/core/hle/service/filesystem/filesystem.cpp | 13 ++++++------- src/core/hle/service/filesystem/filesystem.h | 6 +++++- src/yuzu/main.cpp | 3 +-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 1a318b233..914315d20 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -345,16 +345,15 @@ void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { if (bis_factory == nullptr) bis_factory = std::make_unique(nand_directory); - - auto savedata = std::make_unique(std::move(nand_directory)); - save_data_factory = std::move(savedata); - - auto sdcard = std::make_unique(std::move(sd_directory)); - sdmc_factory = std::move(sdcard); + if (save_data_factory == nullptr) + save_data_factory = std::make_unique(std::move(nand_directory)); + if (sdmc_factory == nullptr) + sdmc_factory = std::make_unique(std::move(sd_directory)); } void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) { - RegisterFileSystems(vfs); + romfs_factory = nullptr; + CreateFactories(vfs, false); std::make_shared()->InstallAsService(service_manager); std::make_shared()->InstallAsService(service_manager); std::make_shared()->InstallAsService(service_manager); diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index df78be44a..d88a66825 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -46,8 +46,12 @@ ResultVal OpenSDMC(); std::shared_ptr GetSystemNANDContents(); std::shared_ptr GetUserNANDContents(); +std::shared_ptr GetSDMCContents(); + +// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function +// above is called. +void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true); -/// Registers all Filesystem services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs); // A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index c62360bd4..bbd1feb85 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -122,8 +122,7 @@ GMainWindow::GMainWindow() show(); // Necessary to load titles from nand in gamelist. - Service::FileSystem::RegisterBIS(std::make_unique(vfs->OpenDirectory( - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite))); + Service::FileSystem::CreateFactories(vfs); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); // Show one-time "callout" messages to the user From bf33f80fae1e97f48a62e16b1e965d7994ac4c45 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:05:30 -0400 Subject: [PATCH 09/24] vfs: Add GetOrCreateDirectoryRelative method --- src/core/file_sys/bis_factory.cpp | 11 ++--------- src/core/file_sys/vfs.cpp | 7 +++++++ src/core/file_sys/vfs.h | 4 ++++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index ae4e33800..08a7cea5a 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -6,19 +6,12 @@ namespace FileSys { -static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) { - const auto res = dir->GetDirectoryRelative(path); - if (res == nullptr) - return dir->CreateDirectoryRelative(path); - return res; -} - BISFactory::BISFactory(VirtualDir nand_root_) : nand_root(std::move(nand_root_)), sysnand_cache(std::make_shared( - GetOrCreateDirectory(nand_root, "/system/Contents/registered"))), + GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))), usrnand_cache(std::make_shared( - GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {} + GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {} std::shared_ptr BISFactory::GetSystemNANDContents() const { return sysnand_cache; diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index b915b4c11..146c839f4 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -462,4 +462,11 @@ bool VfsRawCopy(VirtualFile src, VirtualFile dest) { std::vector data = src->ReadAllBytes(); return dest->WriteBytes(data, 0) == data.size(); } + +VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) { + const auto res = rel->GetDirectoryRelative(path); + if (res == nullptr) + return rel->CreateDirectoryRelative(path); + return res; +} } // namespace FileSys diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 22db08b59..5142a3e86 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -318,4 +318,8 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block // directory of src/dest. bool VfsRawCopy(VirtualFile src, VirtualFile dest); +// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not +// it attempts to create it and returns the new dir or nullptr on failure. +VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path); + } // namespace FileSys From 4112dd6b4ecb35fcbdfb65786ea5c6c57c95f1ef Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:06:24 -0400 Subject: [PATCH 10/24] qt: Make default row data title name and title id Helps with installed games by making the title not a hexadecimal id string, instead the name. --- src/yuzu/configuration/config.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 76c4cb165..60b6d6d44 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -126,8 +126,8 @@ void Config::ReadValues() { qt_config->beginGroup("UIGameList"); UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt(); - UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 0).toUInt(); - UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 3).toUInt(); + UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 3).toUInt(); + UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 2).toUInt(); qt_config->endGroup(); qt_config->beginGroup("UILayout"); From 13524578b602a264a3925c5f4a2ce9607541d2fc Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:06:56 -0400 Subject: [PATCH 11/24] sdmc_factory: Add SDMC RegisteredCache getter --- src/core/file_sys/sdmc_factory.cpp | 12 +++++++++++- src/core/file_sys/sdmc_factory.h | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index d1acf379f..b14a254c5 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -4,13 +4,23 @@ #include #include "core/file_sys/sdmc_factory.h" +#include "core/file_sys/xts_archive.h" namespace FileSys { -SDMCFactory::SDMCFactory(VirtualDir dir) : dir(std::move(dir)) {} +SDMCFactory::SDMCFactory(VirtualDir dir_) + : dir(std::move(dir_)), contents(std::make_shared( + GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"), + [](const VirtualFile& file, const NcaID& id) { + return std::make_shared(file, id)->GetDecrypted(); + })) {} ResultVal SDMCFactory::Open() { return MakeResult(dir); } +std::shared_ptr SDMCFactory::GetSDMCContents() const { + return contents; +} + } // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 245060690..bb579472c 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -15,9 +15,12 @@ public: explicit SDMCFactory(VirtualDir dir); ResultVal Open(); + std::shared_ptr GetSDMCContents() const; private: VirtualDir dir; + + std::shared_ptr contents; }; } // namespace FileSys From 8b52d6682a00d2d0ac025433f034d9974584c630 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:07:37 -0400 Subject: [PATCH 12/24] registration: Add GetEntryUnparsed methods Returns the file before calling parser on it. --- src/core/file_sys/registered_cache.cpp | 12 ++++++++++++ src/core/file_sys/registered_cache.h | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index e90dc6695..a128fa33d 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -262,6 +262,18 @@ bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const { return GetEntryRaw(entry) != nullptr; } +VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const { + const auto id = GetNcaIDFromMetadata(title_id, type); + if (id == boost::none) + return nullptr; + + return GetFileAtID(id.get()); +} + +VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const { + return GetEntryUnparsed(entry.title_id, entry.type); +} + VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const { const auto id = GetNcaIDFromMetadata(title_id, type); if (id == boost::none) diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index a7c51a59c..f48cf3146 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -69,6 +69,9 @@ public: bool HasEntry(u64 title_id, ContentRecordType type) const; bool HasEntry(RegisteredCacheEntry entry) const; + VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const; + VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const; + VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const; VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const; From ab44192ab01a27115e14a52d4823722cbd357c0d Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:08:13 -0400 Subject: [PATCH 13/24] file_sys: Implement NAX containers --- src/core/CMakeLists.txt | 6 ++ src/core/file_sys/xts_archive.cpp | 164 ++++++++++++++++++++++++++++++ src/core/file_sys/xts_archive.h | 68 +++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 src/core/file_sys/xts_archive.cpp create mode 100644 src/core/file_sys/xts_archive.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 31a7bf6fd..a74270a0f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(core STATIC crypto/key_manager.h crypto/ctr_encryption_layer.cpp crypto/ctr_encryption_layer.h + crypto/xts_encryption_layer.cpp + crypto/xts_encryption_layer.h file_sys/bis_factory.cpp file_sys/bis_factory.h file_sys/card_image.cpp @@ -57,6 +59,8 @@ add_library(core STATIC file_sys/vfs_real.h file_sys/vfs_vector.cpp file_sys/vfs_vector.h + file_sys/xts_archive.cpp + file_sys/xts_archive.h frontend/emu_window.cpp frontend/emu_window.h frontend/framebuffer_layout.cpp @@ -347,6 +351,8 @@ add_library(core STATIC loader/linker.h loader/loader.cpp loader/loader.h + loader/nax.cpp + loader/nax.h loader/nca.cpp loader/nca.h loader/nro.cpp diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp new file mode 100644 index 000000000..54be31916 --- /dev/null +++ b/src/core/file_sys/xts_archive.cpp @@ -0,0 +1,164 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include "common/assert.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "core/crypto/aes_util.h" +#include "core/crypto/xts_encryption_layer.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" + +namespace FileSys { + +template +static bool CalculateHMAC256(Destination* out, const SourceKey* key, size_t key_length, + const SourceData* data, size_t data_length) { + mbedtls_md_context_t context; + mbedtls_md_init(&context); + + const auto key_f = reinterpret_cast(key); + const std::vector key_v(key_f, key_f + key_length); + + if (mbedtls_md_setup(&context, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1) || + mbedtls_md_hmac_starts(&context, reinterpret_cast(key), key_length) || + mbedtls_md_hmac_update(&context, reinterpret_cast(data), data_length) || + mbedtls_md_hmac_finish(&context, reinterpret_cast(out))) { + mbedtls_md_free(&context); + return false; + } + + mbedtls_md_free(&context); + return true; +} + +NAX::NAX(VirtualFile file_) : file(std::move(file_)), header(std::make_unique()) { + std::string path = FileUtil::SanitizePath(file->GetFullPath()); + static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca", + std::regex_constants::ECMAScript | + std::regex_constants::icase); + std::smatch match; + if (!std::regex_search(path, match, nax_path_regex)) { + status = Loader::ResultStatus::ErrorBadNAXFilePath; + return; + } + + std::string two_dir = match[1]; + std::string nca_id = match[2]; + std::transform(two_dir.begin(), two_dir.end(), two_dir.begin(), ::toupper); + std::transform(nca_id.begin(), nca_id.end(), nca_id.begin(), ::tolower); + + status = Parse(fmt::format("/registered/{}/{}.nca", two_dir, nca_id)); +} + +NAX::NAX(VirtualFile file_, std::array nca_id) + : file(std::move(file_)), header(std::make_unique()) { + Core::Crypto::SHA256Hash hash{}; + mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); + status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0], + Common::HexArrayToString(nca_id, false))); +} + +Loader::ResultStatus NAX::Parse(std::string path) { + if (file->ReadObject(header.get()) != sizeof(NAXHeader)) + return Loader::ResultStatus::ErrorBadNAXHeader; + + if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) + return Loader::ResultStatus::ErrorBadNAXHeader; + + if (file->GetSize() < 0x4000 + header->file_size) + return Loader::ResultStatus::ErrorIncorrectNAXFileSize; + + keys.DeriveSDSeedLazy(); + std::array sd_keys{}; + const auto sd_keys_res = Core::Crypto::DeriveSDKeys(sd_keys, keys); + if (sd_keys_res != Loader::ResultStatus::Success) { + return sd_keys_res; + } + + const auto enc_keys = header->key_area; + + size_t i = 0; + for (; i < 2; ++i) { + std::array nax_keys{}; + if (!CalculateHMAC256(nax_keys.data(), sd_keys[i].data(), 0x10, path.c_str(), + path.size())) { + return Loader::ResultStatus::ErrorNAXKeyHMACFailed; + } + + for (size_t j = 0; j < 2; ++j) { + Core::Crypto::AESCipher cipher(nax_keys[j], + Core::Crypto::Mode::ECB); + cipher.Transcode(enc_keys[j].data(), 0x10, header->key_area[j].data(), + Core::Crypto::Op::Decrypt); + } + + Core::Crypto::SHA256Hash validation{}; + if (!CalculateHMAC256(validation.data(), &header->magic, 0x60, sd_keys[i].data() + 0x10, + 0x10)) { + return Loader::ResultStatus::ErrorNAXValidationHMACFailed; + } + if (header->hmac == validation) + break; + } + + if (i == 2) { + return Loader::ResultStatus::ErrorNAXKeyDerivationFailed; + } + + type = static_cast(i); + + Core::Crypto::Key256 final_key{}; + memcpy(final_key.data(), &header->key_area, 0x20); + const auto enc_file = std::make_shared(file, header->file_size, 0x4000); + dec_file = std::make_shared(enc_file, final_key); + + return Loader::ResultStatus::Success; +} + +Loader::ResultStatus NAX::GetStatus() { + return status; +} + +VirtualFile NAX::GetDecrypted() { + return dec_file; +} + +std::shared_ptr NAX::AsNCA() { + if (type == NAXContentType::NCA) + return std::make_shared(GetDecrypted()); + return nullptr; +} + +NAXContentType NAX::GetContentType() { + return type; +} + +std::vector> NAX::GetFiles() const { + return {dec_file}; +} + +std::vector> NAX::GetSubdirectories() const { + return {}; +} + +std::string NAX::GetName() const { + return file->GetName(); +} + +std::shared_ptr NAX::GetParentDirectory() const { + return file->GetContainingDirectory(); +} + +bool NAX::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { + return false; +} +} // namespace FileSys diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h new file mode 100644 index 000000000..4e44f634a --- /dev/null +++ b/src/core/file_sys/xts_archive.h @@ -0,0 +1,68 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" +#include "common/swap.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/vfs.h" +#include "core/loader/loader.h" + +namespace FileSys { + +struct NAXHeader { + std::array hmac; + u64_le magic; + std::array key_area; + u64_le file_size; + INSERT_PADDING_BYTES(0x30); +}; +static_assert(sizeof(NAXHeader) == 0x80, "NAXHeader has incorrect size."); + +enum class NAXContentType : u8 { + Save = 0, + NCA = 1, +}; + +class NAX : public ReadOnlyVfsDirectory { +public: + explicit NAX(VirtualFile file); + explicit NAX(VirtualFile file, std::array nca_id); + + Loader::ResultStatus GetStatus(); + + VirtualFile GetDecrypted(); + + std::shared_ptr AsNCA(); + + NAXContentType GetContentType(); + + std::vector> GetFiles() const override; + + std::vector> GetSubdirectories() const override; + + std::string GetName() const override; + + std::shared_ptr GetParentDirectory() const override; + +protected: + bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; + +private: + Loader::ResultStatus Parse(std::string path); + + std::unique_ptr header; + + VirtualFile file; + Loader::ResultStatus status; + NAXContentType type; + + VirtualFile dec_file; + + Core::Crypto::KeyManager keys; +}; +} // namespace FileSys From 60b7a3b90448daf2eb0dabd6edadf42e50b3f5b6 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:08:44 -0400 Subject: [PATCH 14/24] game_list: Add SD registration loading to game list --- src/yuzu/game_list.cpp | 22 +++++++++++----------- src/yuzu/game_list_p.h | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index d5726b8b3..867a3c6f1 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -426,13 +426,12 @@ static void GetMetadataFromControlNCA(const std::shared_ptr& nca, } } -void GameListWorker::AddInstalledTitlesToGameList() { - const auto usernand = Service::FileSystem::GetUserNANDContents(); - const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application, - FileSys::ContentRecordType::Program); +void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr cache) { + const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application, + FileSys::ContentRecordType::Program); for (const auto& game : installed_games) { - const auto& file = usernand->GetEntryRaw(game); + const auto& file = cache->GetEntryUnparsed(game); std::unique_ptr loader = Loader::GetLoader(file); if (!loader) continue; @@ -442,8 +441,7 @@ void GameListWorker::AddInstalledTitlesToGameList() { u64 program_id = 0; loader->ReadProgramId(program_id); - const auto& control = - usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control); + const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); if (control != nullptr) GetMetadataFromControlNCA(control, icon, name); emit EntryReady({ @@ -457,11 +455,11 @@ void GameListWorker::AddInstalledTitlesToGameList() { }); } - const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application, - FileSys::ContentRecordType::Control); + const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application, + FileSys::ContentRecordType::Control); for (const auto& entry : control_data) { - const auto nca = usernand->GetEntry(entry); + const auto nca = cache->GetEntry(entry); if (nca != nullptr) nca_control_map.insert_or_assign(entry.title_id, nca); } @@ -549,7 +547,9 @@ void GameListWorker::run() { stop_processing = false; watch_list.append(dir_path); FillControlMap(dir_path.toStdString()); - AddInstalledTitlesToGameList(); + AddInstalledTitlesToGameList(Service::FileSystem::GetUserNANDContents()); + AddInstalledTitlesToGameList(Service::FileSystem::GetSystemNANDContents()); + AddInstalledTitlesToGameList(Service::FileSystem::GetSDMCContents()); AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); nca_control_map.clear(); emit Finished(watch_list); diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index c59613769..1d6c85400 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -172,7 +172,7 @@ private: bool deep_scan; std::atomic_bool stop_processing; - void AddInstalledTitlesToGameList(); + void AddInstalledTitlesToGameList(std::shared_ptr cache); void FillControlMap(const std::string& dir_path); void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); }; From cde665c56514c1b701c0fe94fc943c7692be7f32 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:10:01 -0400 Subject: [PATCH 15/24] key_manager: Switch to boost flat_map for keys Should make key gets marginally faster. --- src/core/crypto/key_manager.cpp | 5 ++-- src/core/crypto/key_manager.h | 41 +++++++++------------------------ 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index db8b22c85..95158e630 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -125,7 +125,8 @@ bool KeyManager::KeyFileExists(bool title) { FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); } -const std::unordered_map> KeyManager::s128_file_id = { +void KeyManager::DeriveSDSeedLazy() { +const boost::container::flat_map> KeyManager::s128_file_id = { {"master_key_00", {S128KeyType::Master, 0, 0}}, {"master_key_01", {S128KeyType::Master, 1, 0}}, {"master_key_02", {S128KeyType::Master, 2, 0}}, @@ -169,7 +170,7 @@ const std::unordered_map> KeyManager::s128_fi {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast(KeyAreaKeyType::System)}}, }; -const std::unordered_map> KeyManager::s256_file_id = { +const boost::container::flat_map> KeyManager::s256_file_id = { {"header_key", {S256KeyType::Header, 0, 0}}, {"sd_card_save_key", {S256KeyType::SDSave, 0, 0}}, {"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}}, diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 0c62d4421..33b1ad383 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -7,10 +7,11 @@ #include #include #include -#include #include +#include #include #include "common/common_types.h" +#include "core/loader/loader.h" namespace Core::Crypto { @@ -59,34 +60,14 @@ struct KeyIndex { } }; -// The following two (== and hash) are so KeyIndex can be a key in unordered_map - +// boost flat_map requires operator< for O(log(n)) lookups. template -bool operator==(const KeyIndex& lhs, const KeyIndex& rhs) { - return std::tie(lhs.type, lhs.field1, lhs.field2) == std::tie(rhs.type, rhs.field1, rhs.field2); +bool operator<(const KeyIndex& lhs, const KeyIndex& rhs) { + return (static_cast(lhs.type) < static_cast(rhs.type)) || + (lhs.type == rhs.type && lhs.field1 < rhs.field1) || + (lhs.type == rhs.type && lhs.field1 == rhs.field1 && lhs.field2 < rhs.field2); } -template -bool operator!=(const KeyIndex& lhs, const KeyIndex& rhs) { - return !operator==(lhs, rhs); -} - -} // namespace Core::Crypto - -namespace std { -template -struct hash> { - size_t operator()(const Core::Crypto::KeyIndex& k) const { - using std::hash; - - return ((hash()(static_cast(k.type)) ^ (hash()(k.field1) << 1)) >> 1) ^ - (hash()(k.field2) << 1); - } -}; -} // namespace std - -namespace Core::Crypto { - class KeyManager { public: KeyManager(); @@ -103,15 +84,15 @@ public: static bool KeyFileExists(bool title); private: - std::unordered_map, Key128> s128_keys; - std::unordered_map, Key256> s256_keys; + boost::container::flat_map, Key128> s128_keys; + boost::container::flat_map, Key256> s256_keys; bool dev_mode; void LoadFromFile(const std::string& filename, bool is_title_keys); void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, const std::string& filename, bool title); - static const std::unordered_map> s128_file_id; - static const std::unordered_map> s256_file_id; + static const boost::container::flat_map> s128_file_id; + static const boost::container::flat_map> s256_file_id; }; } // namespace Core::Crypto From f26fc64cb4f36fbc9be05ed153f32d8f4dd3850c Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:12:05 -0400 Subject: [PATCH 16/24] key_manager: Add support for KEK and SD seed derivation --- src/core/crypto/key_manager.cpp | 114 +++++++++++++++++++++++++++++++- src/core/crypto/key_manager.h | 26 +++++++- 2 files changed, 135 insertions(+), 5 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 95158e630..e4b33f750 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -12,11 +12,105 @@ #include "common/file_util.h" #include "common/hex_util.h" #include "common/logging/log.h" +#include "core/crypto/aes_util.h" #include "core/crypto/key_manager.h" #include "core/settings.h" namespace Core::Crypto { +Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { + Key128 out{}; + + AESCipher cipher1(master, Mode::ECB); + cipher1.Transcode(kek_seed.data(), kek_seed.size(), out.data(), Op::Decrypt); + AESCipher cipher2(out, Mode::ECB); + cipher2.Transcode(source.data(), source.size(), out.data(), Op::Decrypt); + + if (key_seed != Key128{}) { + AESCipher cipher3(out, Mode::ECB); + cipher3.Transcode(key_seed.data(), key_seed.size(), out.data(), Op::Decrypt); + } + + return out; +} + +boost::optional DeriveSDSeed() { + const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "/system/save/8000000000000043", + "rb+"); + if (!save_43.IsOpen()) + return boost::none; + const FileUtil::IOFile sd_private( + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+"); + if (!sd_private.IsOpen()) + return boost::none; + + sd_private.Seek(0, SEEK_SET); + std::array private_seed{}; + if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != 0x10) + return boost::none; + + std::array buffer{}; + size_t offset = 0; + for (; offset + 0x10 < save_43.GetSize(); ++offset) { + save_43.Seek(offset, SEEK_SET); + save_43.ReadBytes(buffer.data(), buffer.size()); + if (buffer == private_seed) + break; + } + + if (offset + 0x10 >= save_43.GetSize()) + return boost::none; + + Key128 seed{}; + save_43.Seek(offset + 0x10, SEEK_SET); + save_43.ReadBytes(seed.data(), seed.size()); + return seed; +} + +Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, const KeyManager& keys) { + if (!keys.HasKey(S128KeyType::Source, static_cast(SourceKeyType::SDKEK))) + return Loader::ResultStatus::ErrorMissingSDKEKSource; + if (!keys.HasKey(S128KeyType::Source, static_cast(SourceKeyType::AESKEKGeneration))) + return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource; + if (!keys.HasKey(S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration))) + return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource; + + const auto sd_kek_source = + keys.GetKey(S128KeyType::Source, static_cast(SourceKeyType::SDKEK)); + const auto aes_kek_gen = + keys.GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKEKGeneration)); + const auto aes_key_gen = + keys.GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration)); + const auto master_00 = keys.GetKey(S128KeyType::Master); + const auto sd_kek = + GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen); + + if (!keys.HasKey(S128KeyType::SDSeed)) + return Loader::ResultStatus::ErrorMissingSDSeed; + const auto sd_seed = keys.GetKey(S128KeyType::SDSeed); + + if (!keys.HasKey(S256KeyType::SDKeySource, static_cast(SDKeyType::Save))) + return Loader::ResultStatus::ErrorMissingSDSaveKeySource; + if (!keys.HasKey(S256KeyType::SDKeySource, static_cast(SDKeyType::NCA))) + return Loader::ResultStatus::ErrorMissingSDNCAKeySource; + + std::array sd_key_sources{ + keys.GetKey(S256KeyType::SDKeySource, static_cast(SDKeyType::Save)), + keys.GetKey(S256KeyType::SDKeySource, static_cast(SDKeyType::NCA)), + }; + + AESCipher cipher(sd_kek, Mode::ECB); + for (size_t i = 0; i < 2; ++i) { + for (size_t j = 0; j < 0x20; ++j) + sd_key_sources[i][j] ^= sd_seed[j & 0xF]; + cipher.Transcode(sd_key_sources[i].data(), sd_key_sources[i].size(), sd_keys[i].data(), + Op::Decrypt); + } + + return Loader::ResultStatus::Success; +} + KeyManager::KeyManager() { // Initialize keys const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); @@ -24,12 +118,15 @@ KeyManager::KeyManager() { if (Settings::values.use_dev_keys) { dev_mode = true; AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "dev.keys_autogenerated", false); } else { dev_mode = false; AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false); + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "prod.keys_autogenerated", false); } AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", false); } void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { @@ -126,6 +223,13 @@ bool KeyManager::KeyFileExists(bool title) { } void KeyManager::DeriveSDSeedLazy() { + if (!HasKey(S128KeyType::SDSeed)) { + const auto res = DeriveSDSeed(); + if (res != boost::none) + SetKey(S128KeyType::SDSeed, res.get()); + } +} + const boost::container::flat_map> KeyManager::s128_file_id = { {"master_key_00", {S128KeyType::Master, 0, 0}}, {"master_key_01", {S128KeyType::Master, 1, 0}}, @@ -168,11 +272,17 @@ const boost::container::flat_map> KeyManager: {"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast(KeyAreaKeyType::System)}}, {"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast(KeyAreaKeyType::System)}}, {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast(KeyAreaKeyType::System)}}, + {"sd_card_kek_source", {S128KeyType::Source, static_cast(SourceKeyType::SDKEK), 0}}, + {"aes_kek_generation_source", + {S128KeyType::Source, static_cast(SourceKeyType::AESKEKGeneration), 0}}, + {"aes_key_generation_source", + {S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration), 0}}, + {"sd_seed", {S128KeyType::SDSeed, 0, 0}}, }; const boost::container::flat_map> KeyManager::s256_file_id = { {"header_key", {S256KeyType::Header, 0, 0}}, - {"sd_card_save_key", {S256KeyType::SDSave, 0, 0}}, - {"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}}, + {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast(SDKeyType::Save), 0}}, + {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast(SDKeyType::NCA), 0}}, }; } // namespace Core::Crypto diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 33b1ad383..31040dc55 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -23,9 +23,8 @@ static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big."); enum class S256KeyType : u64 { - Header, // - SDSave, // - SDNCA, // + Header, // + SDKeySource, // f1=SDKeyType }; enum class S128KeyType : u64 { @@ -37,6 +36,7 @@ enum class S128KeyType : u64 { KeyArea, // f1=crypto revision f2=type {app, ocean, system} SDSeed, // Titlekey, // f1=rights id LSB f2=rights id MSB + Source, // f1=source type, f2= sub id }; enum class KeyAreaKeyType : u8 { @@ -45,6 +45,17 @@ enum class KeyAreaKeyType : u8 { System, }; +enum class SourceKeyType : u8 { + SDKEK, + AESKEKGeneration, + AESKeyGeneration, +}; + +enum class SDKeyType : u8 { + Save, + NCA, +}; + template struct KeyIndex { KeyType type; @@ -83,6 +94,10 @@ public: static bool KeyFileExists(bool title); + // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save + // 8*43 and the private file to exist. + void DeriveSDSeedLazy(); + private: boost::container::flat_map, Key128> s128_keys; boost::container::flat_map, Key256> s256_keys; @@ -95,4 +110,9 @@ private: static const boost::container::flat_map> s128_file_id; static const boost::container::flat_map> s256_file_id; }; + +Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); +boost::optional DeriveSDSeed(); +Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, const KeyManager& keys); + } // namespace Core::Crypto From 61a5b56abde4a87e1e66c76b506bdd4dada58389 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 16 Aug 2018 17:12:31 -0400 Subject: [PATCH 17/24] key_manager: Add support for autogenerated keys Stored in a separate file than manual keys. --- src/core/crypto/key_manager.cpp | 46 ++++++++++++++++++++++++++++++--- src/core/crypto/key_manager.h | 2 ++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index e4b33f750..994ac4eec 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -153,17 +153,17 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { u128 rights_id{}; std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); Key128 key = Common::HexStringToArray<16>(out[1]); - SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key; } else { std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower); if (s128_file_id.find(out[0]) != s128_file_id.end()) { const auto index = s128_file_id.at(out[0]); Key128 key = Common::HexStringToArray<16>(out[1]); - SetKey(index.type, key, index.field1, index.field2); + s128_keys[{index.type, index.field1, index.field2}] = key; } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { const auto index = s256_file_id.at(out[0]); Key256 key = Common::HexStringToArray<32>(out[1]); - SetKey(index.type, key, index.field1, index.field2); + s256_keys[{index.type, index.field1, index.field2}] = key; } } } @@ -197,11 +197,51 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { return s256_keys.at({id, field1, field2}); } +template +void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, + std::array key) { + const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); + std::string filename = "title.keys_autogenerated"; + if (!title_key) + filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; + const auto add_info_text = !FileUtil::Exists(yuzu_keys_dir + DIR_SEP + filename); + std::ofstream file(yuzu_keys_dir + DIR_SEP + filename, std::ios::app); + if (!file.is_open()) + return; + if (add_info_text) { + file << "# This file is autogenerated by Yuzu" << std::endl + << "# It serves to store keys that were automatically generated from the normal keys" + << std::endl + << "# If you are experiencing issues involving keys, it may help to delete this file" + << std::endl; + } + + file << std::endl + << fmt::format("{} = {}", keyname, Common::HexArrayToString(key)) << std::endl; + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key); +} + void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { + const auto iter = std::find_if( + s128_file_id.begin(), s128_file_id.end(), + [&id, &field1, &field2](const std::pair> elem) { + return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == + std::tie(id, field1, field2); + }); + if (iter != s128_file_id.end()) + WriteKeyToFile(id == S128KeyType::Titlekey, iter->first, key); s128_keys[{id, field1, field2}] = key; } void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { + const auto iter = std::find_if( + s256_file_id.begin(), s256_file_id.end(), + [&id, &field1, &field2](const std::pair> elem) { + return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == + std::tie(id, field1, field2); + }); + if (iter != s256_file_id.end()) + WriteKeyToFile(false, iter->first, key); s256_keys[{id, field1, field2}] = key; } diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 31040dc55..78d0b64e0 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -106,6 +106,8 @@ private: void LoadFromFile(const std::string& filename, bool is_title_keys); void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, const std::string& filename, bool title); + template + void WriteKeyToFile(bool title_key, std::string_view keyname, std::array key); static const boost::container::flat_map> s128_file_id; static const boost::container::flat_map> s256_file_id; From 42dc856ce136c75f587649863ec5bd936ba8b07a Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 18 Aug 2018 21:14:57 -0400 Subject: [PATCH 18/24] crypto: Eliminate magic constants --- src/core/crypto/key_manager.cpp | 2 +- src/core/crypto/xts_encryption_layer.cpp | 33 +++++++++++++----------- src/core/file_sys/xts_archive.cpp | 25 ++++++++++-------- src/core/file_sys/xts_archive.h | 10 +++---- 4 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 994ac4eec..acf635a65 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -102,7 +102,7 @@ Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, const KeyManag AESCipher cipher(sd_kek, Mode::ECB); for (size_t i = 0; i < 2; ++i) { - for (size_t j = 0; j < 0x20; ++j) + for (size_t j = 0; j < sd_key_sources[i].size(); ++j) sd_key_sources[i][j] ^= sd_seed[j & 0xF]; cipher.Transcode(sd_key_sources[i].data(), sd_key_sources[i].size(), sd_keys[i].data(), Op::Decrypt); diff --git a/src/core/crypto/xts_encryption_layer.cpp b/src/core/crypto/xts_encryption_layer.cpp index 431099580..c6e5df1ce 100644 --- a/src/core/crypto/xts_encryption_layer.cpp +++ b/src/core/crypto/xts_encryption_layer.cpp @@ -8,6 +8,8 @@ namespace Core::Crypto { +constexpr u64 XTS_SECTOR_SIZE = 0x4000; + XTSEncryptionLayer::XTSEncryptionLayer(FileSys::VirtualFile base_, Key256 key_) : EncryptionLayer(std::move(base_)), cipher(key_, Mode::XTS) {} @@ -17,34 +19,35 @@ size_t XTSEncryptionLayer::Read(u8* data, size_t length, size_t offset) const { const auto sector_offset = offset & 0x3FFF; if (sector_offset == 0) { - if (length % 0x4000 == 0) { + if (length % XTS_SECTOR_SIZE == 0) { std::vector raw = base->ReadBytes(length, offset); - cipher.XTSTranscode(raw.data(), raw.size(), data, offset / 0x4000, 0x4000, Op::Decrypt); + cipher.XTSTranscode(raw.data(), raw.size(), data, offset / XTS_SECTOR_SIZE, + XTS_SECTOR_SIZE, Op::Decrypt); return raw.size(); } - if (length > 0x4000) { - const auto rem = length % 0x4000; + if (length > XTS_SECTOR_SIZE) { + const auto rem = length % XTS_SECTOR_SIZE; const auto read = length - rem; return Read(data, read, offset) + Read(data + read, rem, offset + read); } - std::vector buffer = base->ReadBytes(0x4000, offset); - if (buffer.size() < 0x4000) - buffer.resize(0x4000); - cipher.XTSTranscode(buffer.data(), buffer.size(), buffer.data(), offset / 0x4000, 0x4000, - Op::Decrypt); + std::vector buffer = base->ReadBytes(XTS_SECTOR_SIZE, offset); + if (buffer.size() < XTS_SECTOR_SIZE) + buffer.resize(XTS_SECTOR_SIZE); + cipher.XTSTranscode(buffer.data(), buffer.size(), buffer.data(), offset / XTS_SECTOR_SIZE, + XTS_SECTOR_SIZE, Op::Decrypt); std::memcpy(data, buffer.data(), std::min(buffer.size(), length)); return std::min(buffer.size(), length); } // offset does not fall on block boundary (0x4000) std::vector block = base->ReadBytes(0x4000, offset - sector_offset); - if (block.size() < 0x4000) - block.resize(0x4000); - cipher.XTSTranscode(block.data(), block.size(), block.data(), (offset - sector_offset) / 0x4000, - 0x4000, Op::Decrypt); - const size_t read = 0x4000 - sector_offset; + if (block.size() < XTS_SECTOR_SIZE) + block.resize(XTS_SECTOR_SIZE); + cipher.XTSTranscode(block.data(), block.size(), block.data(), + (offset - sector_offset) / XTS_SECTOR_SIZE, XTS_SECTOR_SIZE, Op::Decrypt); + const size_t read = XTS_SECTOR_SIZE - sector_offset; - if (length + sector_offset < 0x4000) { + if (length + sector_offset < XTS_SECTOR_SIZE) { std::memcpy(data, block.data() + sector_offset, std::min(length, read)); return std::min(length, read); } diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index 54be31916..605c1a283 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp @@ -19,6 +19,8 @@ namespace FileSys { +constexpr u64 NAX_HEADER_PADDING_SIZE = 0x4000; + template static bool CalculateHMAC256(Destination* out, const SourceKey* key, size_t key_length, const SourceData* data, size_t data_length) { @@ -67,14 +69,14 @@ NAX::NAX(VirtualFile file_, std::array nca_id) Common::HexArrayToString(nca_id, false))); } -Loader::ResultStatus NAX::Parse(std::string path) { +Loader::ResultStatus NAX::Parse(std::string_view path) { if (file->ReadObject(header.get()) != sizeof(NAXHeader)) return Loader::ResultStatus::ErrorBadNAXHeader; if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) return Loader::ResultStatus::ErrorBadNAXHeader; - if (file->GetSize() < 0x4000 + header->file_size) + if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) return Loader::ResultStatus::ErrorIncorrectNAXFileSize; keys.DeriveSDSeedLazy(); @@ -87,14 +89,14 @@ Loader::ResultStatus NAX::Parse(std::string path) { const auto enc_keys = header->key_area; size_t i = 0; - for (; i < 2; ++i) { + for (; i < sd_keys.size(); ++i) { std::array nax_keys{}; - if (!CalculateHMAC256(nax_keys.data(), sd_keys[i].data(), 0x10, path.c_str(), + if (!CalculateHMAC256(nax_keys.data(), sd_keys[i].data(), 0x10, std::string(path).c_str(), path.size())) { return Loader::ResultStatus::ErrorNAXKeyHMACFailed; } - for (size_t j = 0; j < 2; ++j) { + for (size_t j = 0; j < nax_keys.size(); ++j) { Core::Crypto::AESCipher cipher(nax_keys[j], Core::Crypto::Mode::ECB); cipher.Transcode(enc_keys[j].data(), 0x10, header->key_area[j].data(), @@ -117,28 +119,29 @@ Loader::ResultStatus NAX::Parse(std::string path) { type = static_cast(i); Core::Crypto::Key256 final_key{}; - memcpy(final_key.data(), &header->key_area, 0x20); - const auto enc_file = std::make_shared(file, header->file_size, 0x4000); + std::memcpy(final_key.data(), &header->key_area, final_key.size()); + const auto enc_file = + std::make_shared(file, header->file_size, NAX_HEADER_PADDING_SIZE); dec_file = std::make_shared(enc_file, final_key); return Loader::ResultStatus::Success; } -Loader::ResultStatus NAX::GetStatus() { +Loader::ResultStatus NAX::GetStatus() const { return status; } -VirtualFile NAX::GetDecrypted() { +VirtualFile NAX::GetDecrypted() const { return dec_file; } -std::shared_ptr NAX::AsNCA() { +std::shared_ptr NAX::AsNCA() const { if (type == NAXContentType::NCA) return std::make_shared(GetDecrypted()); return nullptr; } -NAXContentType NAX::GetContentType() { +NAXContentType NAX::GetContentType() const { return type; } diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h index 4e44f634a..5249ad026 100644 --- a/src/core/file_sys/xts_archive.h +++ b/src/core/file_sys/xts_archive.h @@ -33,13 +33,13 @@ public: explicit NAX(VirtualFile file); explicit NAX(VirtualFile file, std::array nca_id); - Loader::ResultStatus GetStatus(); + Loader::ResultStatus GetStatus() const; - VirtualFile GetDecrypted(); + VirtualFile GetDecrypted() const; - std::shared_ptr AsNCA(); + std::shared_ptr AsNCA() const; - NAXContentType GetContentType(); + NAXContentType GetContentType() const; std::vector> GetFiles() const override; @@ -53,7 +53,7 @@ protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; private: - Loader::ResultStatus Parse(std::string path); + Loader::ResultStatus Parse(std::string_view path); std::unique_ptr header; From a7e8d10969f280cd5a869b3525c3339357a958a6 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 18 Aug 2018 21:16:20 -0400 Subject: [PATCH 19/24] file_sys: Cut down on includes and copies --- src/core/crypto/key_manager.cpp | 22 +++++++++++----------- src/core/crypto/key_manager.h | 6 ++---- src/core/file_sys/registered_cache.cpp | 2 ++ src/core/file_sys/registered_cache.h | 1 + src/core/file_sys/sdmc_factory.h | 3 +++ src/core/loader/nax.cpp | 2 ++ src/core/loader/nax.h | 13 +++++++++---- 7 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index acf635a65..1cb3fce00 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -199,7 +199,7 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { template void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, - std::array key) { + const std::array& key) { const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); std::string filename = "title.keys_autogenerated"; if (!title_key) @@ -209,11 +209,10 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, if (!file.is_open()) return; if (add_info_text) { - file << "# This file is autogenerated by Yuzu" << std::endl - << "# It serves to store keys that were automatically generated from the normal keys" - << std::endl - << "# If you are experiencing issues involving keys, it may help to delete this file" - << std::endl; + file + << "# This file is autogenerated by Yuzu\n" + << "# It serves to store keys that were automatically generated from the normal keys\n" + << "# If you are experiencing issues involving keys, it may help to delete this file\n"; } file << std::endl @@ -263,11 +262,12 @@ bool KeyManager::KeyFileExists(bool title) { } void KeyManager::DeriveSDSeedLazy() { - if (!HasKey(S128KeyType::SDSeed)) { - const auto res = DeriveSDSeed(); - if (res != boost::none) - SetKey(S128KeyType::SDSeed, res.get()); - } + if (HasKey(S128KeyType::SDSeed)) + return; + + const auto res = DeriveSDSeed(); + if (res != boost::none) + SetKey(S128KeyType::SDSeed, res.get()); } const boost::container::flat_map> KeyManager::s128_file_id = { diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 78d0b64e0..7a8728f76 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -74,9 +74,7 @@ struct KeyIndex { // boost flat_map requires operator< for O(log(n)) lookups. template bool operator<(const KeyIndex& lhs, const KeyIndex& rhs) { - return (static_cast(lhs.type) < static_cast(rhs.type)) || - (lhs.type == rhs.type && lhs.field1 < rhs.field1) || - (lhs.type == rhs.type && lhs.field1 == rhs.field1 && lhs.field2 < rhs.field2); + return std::tie(lhs.type, lhs.field1, lhs.field2) < std::tie(rhs.type, rhs.field1, rhs.field2); } class KeyManager { @@ -107,7 +105,7 @@ private: void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, const std::string& filename, bool title); template - void WriteKeyToFile(bool title_key, std::string_view keyname, std::array key); + void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array& key); static const boost::container::flat_map> s128_file_id; static const boost::container::flat_map> s256_file_id; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index a128fa33d..a02efc71e 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -254,6 +254,8 @@ RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction Refresh(); } +RegisteredCache::~RegisteredCache() = default; + bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const { return GetEntryRaw(title_id, type) != nullptr; } diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index f48cf3146..7b8955dfa 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -63,6 +63,7 @@ public: explicit RegisteredCache(VirtualDir dir, RegisteredCacheParsingFunction parsing_function = [](const VirtualFile& file, const NcaID& id) { return file; }); + ~RegisteredCache(); void Refresh(); diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index bb579472c..4eac92621 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -4,11 +4,14 @@ #pragma once +#include #include "core/file_sys/vfs.h" #include "core/hle/result.h" namespace FileSys { +class RegisteredCache; + /// File system interface to the SDCard archive class SDMCFactory { public: diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index 76390bf46..b35fdc3f8 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -6,8 +6,10 @@ #include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/romfs.h" +#include "core/file_sys/xts_archive.h" #include "core/hle/kernel/process.h" #include "core/loader/nax.h" +#include "core/loader/nca.h" namespace Loader { diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index 08d6ef346..4dbae2918 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -6,18 +6,23 @@ #include #include "common/common_types.h" -#include "core/file_sys/card_image.h" -#include "core/file_sys/xts_archive.h" #include "core/loader/loader.h" -#include "core/loader/nca.h" + +namespace FileSys { + +class NAX; + +} // namespace FileSys namespace Loader { +class AppLoader_NCA; + /// Loads a NAX file class AppLoader_NAX final : public AppLoader { public: explicit AppLoader_NAX(FileSys::VirtualFile file); - ~AppLoader_NAX(); + ~AppLoader_NAX() override; /** * Returns the type of the file From 119ab308b551da889782420aeff088aff0d4882e Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Tue, 21 Aug 2018 15:04:03 -0400 Subject: [PATCH 20/24] key_manager: Create keys dir if it dosen't exist On call to WriteKeyToFile, so that the autogenerated file can be written. --- src/core/crypto/key_manager.cpp | 1 + src/core/file_sys/sdmc_factory.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 1cb3fce00..daa779434 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -205,6 +205,7 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, if (!title_key) filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; const auto add_info_text = !FileUtil::Exists(yuzu_keys_dir + DIR_SEP + filename); + FileUtil::CreateFullPath(yuzu_keys_dir + DIR_SEP + filename); std::ofstream file(yuzu_keys_dir + DIR_SEP + filename, std::ios::app); if (!file.is_open()) return; diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index b14a254c5..e4df5c4d0 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include "core/file_sys/registered_cache.h" #include "core/file_sys/sdmc_factory.h" #include "core/file_sys/xts_archive.h" From ccfd17638211140b97cbf43b8486a80221f47c9d Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Tue, 21 Aug 2018 21:12:53 -0400 Subject: [PATCH 21/24] key_manager: Eliminate indexed for loop --- src/core/crypto/key_manager.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index daa779434..0b14bf15c 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -100,14 +100,21 @@ Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, const KeyManag keys.GetKey(S256KeyType::SDKeySource, static_cast(SDKeyType::NCA)), }; - AESCipher cipher(sd_kek, Mode::ECB); - for (size_t i = 0; i < 2; ++i) { - for (size_t j = 0; j < sd_key_sources[i].size(); ++j) - sd_key_sources[i][j] ^= sd_seed[j & 0xF]; - cipher.Transcode(sd_key_sources[i].data(), sd_key_sources[i].size(), sd_keys[i].data(), - Op::Decrypt); + // Combine sources and seed + for (auto& source : sd_key_sources) { + for (size_t i = 0; i < source.size(); ++i) + source[i] ^= sd_seed[i & 0xF]; } + AESCipher cipher(sd_kek, Mode::ECB); + // The transform manipulates sd_keys as part of the Transcode, so the return/output is + // unnecessary. This does not alter sd_keys_sources. + std::transform(sd_key_sources.begin(), sd_key_sources.end(), sd_keys.begin(), + sd_key_sources.begin(), [&cipher](const Key256& source, Key256& out) { + cipher.Transcode(source.data(), source.size(), out.data(), Op::Decrypt); + return source; ///< Return unaltered source to satisfy output requirement. + }); + return Loader::ResultStatus::Success; } From 4f18c17df77e791d30cc8c919108315610f315ef Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 23 Aug 2018 18:53:13 -0400 Subject: [PATCH 22/24] content_archive: Add update title detection This is needed because the title IDs of update NCAs will not use the update title ID. The only sure way to tell is to look for a partition with BKTR crypto. --- src/core/file_sys/content_archive.cpp | 8 ++++++++ src/core/file_sys/content_archive.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 008e11d8c..e8b5d6ece 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -258,6 +258,10 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); } + is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) { + return header.raw.header.crypto_type == NCASectionCryptoType::BKTR; + }) != sections.end(); + for (std::ptrdiff_t i = 0; i < number_sections; ++i) { auto section = sections[i]; @@ -358,6 +362,10 @@ VirtualFile NCA::GetBaseFile() const { return file; } +bool NCA::IsUpdate() const { + return is_update; +} + bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { return false; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 4b74c54ec..b961cfde7 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -93,6 +93,8 @@ public: VirtualFile GetBaseFile() const; + bool IsUpdate() const; + protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; @@ -111,6 +113,7 @@ private: NCAHeader header{}; bool has_rights_id{}; + bool is_update{}; Loader::ResultStatus status{}; From d1a6dd61d1e70d41cf7bc0dbf3a199cb21d8fdbd Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 23 Aug 2018 18:53:37 -0400 Subject: [PATCH 23/24] xci: Ignore NCA files with updates in secure --- src/core/file_sys/card_image.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 508f09e56..d61a2ebe1 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -149,6 +149,9 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { if (file->GetExtension() != "nca") continue; auto nca = std::make_shared(file); + // TODO(DarkLordZach): Add proper Rev1+ Support + if (nca->IsUpdate()) + continue; if (nca->GetType() == NCAContentType::Program) { program_nca_status = nca->GetStatus(); } From 6314a799aa7e20789562d2e877949dfebb6194ce Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Fri, 24 Aug 2018 22:15:32 -0400 Subject: [PATCH 24/24] file_sys/crypto: Fix missing/unnecessary includes --- src/core/crypto/key_manager.cpp | 5 ++--- src/core/crypto/key_manager.h | 1 + src/core/crypto/xts_encryption_layer.cpp | 1 + src/core/crypto/xts_encryption_layer.h | 1 - src/core/file_sys/sdmc_factory.cpp | 2 ++ src/core/file_sys/sdmc_factory.h | 1 + src/core/file_sys/xts_archive.cpp | 2 ++ src/core/file_sys/xts_archive.h | 1 + src/core/loader/nax.cpp | 1 - 9 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 0b14bf15c..0b6c07de8 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -133,7 +133,7 @@ KeyManager::KeyManager() { } AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); - AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", false); + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true); } void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { @@ -223,8 +223,7 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, << "# If you are experiencing issues involving keys, it may help to delete this file\n"; } - file << std::endl - << fmt::format("{} = {}", keyname, Common::HexArrayToString(key)) << std::endl; + file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key)); AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key); } diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 7a8728f76..7ca3e6cbc 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include diff --git a/src/core/crypto/xts_encryption_layer.cpp b/src/core/crypto/xts_encryption_layer.cpp index c6e5df1ce..c10832cfe 100644 --- a/src/core/crypto/xts_encryption_layer.cpp +++ b/src/core/crypto/xts_encryption_layer.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include "common/assert.h" #include "core/crypto/xts_encryption_layer.h" diff --git a/src/core/crypto/xts_encryption_layer.h b/src/core/crypto/xts_encryption_layer.h index 1e1acaf4a..7a1f1dc64 100644 --- a/src/core/crypto/xts_encryption_layer.h +++ b/src/core/crypto/xts_encryption_layer.h @@ -4,7 +4,6 @@ #pragma once -#include #include "core/crypto/aes_util.h" #include "core/crypto/encryption_layer.h" #include "core/crypto/key_manager.h" diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index e4df5c4d0..d66a9c9a4 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -16,6 +16,8 @@ SDMCFactory::SDMCFactory(VirtualDir dir_) return std::make_shared(file, id)->GetDecrypted(); })) {} +SDMCFactory::~SDMCFactory() = default; + ResultVal SDMCFactory::Open() { return MakeResult(dir); } diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 4eac92621..ea12149de 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -16,6 +16,7 @@ class RegisteredCache; class SDMCFactory { public: explicit SDMCFactory(VirtualDir dir); + ~SDMCFactory(); ResultVal Open(); std::shared_ptr GetSDMCContents() const; diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index 605c1a283..552835738 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp @@ -2,7 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include +#include #include #include #include diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h index 5249ad026..55d2154a6 100644 --- a/src/core/file_sys/xts_archive.h +++ b/src/core/file_sys/xts_archive.h @@ -8,6 +8,7 @@ #include #include "common/common_types.h" #include "common/swap.h" +#include "core/crypto/key_manager.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/vfs.h" #include "core/loader/loader.h" diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index b35fdc3f8..b46d81c02 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include "common/logging/log.h" -#include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/romfs.h" #include "core/file_sys/xts_archive.h"