From df5b75694f5abde94ccf05fa6c7a557b1ba9079b Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Fri, 27 Jul 2018 23:55:23 -0400 Subject: [PATCH 01/13] Remove files that are not used --- .gitmodules | 3 + externals/CMakeLists.txt | 4 + externals/mbedtls | 1 + src/common/file_util.cpp | 12 + src/common/file_util.h | 2 + src/common/logging/backend.cpp | 1 + src/common/logging/log.h | 1 + src/core/CMakeLists.txt | 11 + src/core/crypto/aes_util.cpp | 6 + src/core/crypto/aes_util.h | 118 +++++++ src/core/crypto/ctr_encryption_layer.cpp | 56 ++++ src/core/crypto/ctr_encryption_layer.h | 31 ++ src/core/crypto/encryption_layer.cpp | 42 +++ src/core/crypto/encryption_layer.h | 30 ++ src/core/crypto/key_manager.cpp | 410 +++++++++++++++++++++++ src/core/crypto/key_manager.h | 116 +++++++ src/core/crypto/sha_util.cpp | 5 + src/core/crypto/sha_util.h | 20 ++ src/core/file_sys/card_image.cpp | 150 +++++++++ src/core/file_sys/card_image.h | 93 +++++ src/core/file_sys/content_archive.cpp | 168 ++++++++-- src/core/file_sys/content_archive.h | 26 +- src/core/file_sys/vfs.cpp | 21 ++ src/core/file_sys/vfs.h | 3 + src/core/loader/loader.cpp | 9 + src/core/loader/loader.h | 1 + src/core/loader/nca.cpp | 19 +- src/core/loader/nca.h | 2 + src/core/loader/xci.cpp | 67 ++++ src/core/loader/xci.h | 42 +++ src/core/settings.h | 2 + src/yuzu/configuration/config.cpp | 2 + src/yuzu/game_list.cpp | 2 +- src/yuzu/main.cpp | 14 + src/yuzu_cmd/config.cpp | 1 + src/yuzu_cmd/yuzu.cpp | 15 + 36 files changed, 1463 insertions(+), 43 deletions(-) create mode 160000 externals/mbedtls create mode 100644 src/core/crypto/aes_util.cpp create mode 100644 src/core/crypto/aes_util.h create mode 100644 src/core/crypto/ctr_encryption_layer.cpp create mode 100644 src/core/crypto/ctr_encryption_layer.h create mode 100644 src/core/crypto/encryption_layer.cpp create mode 100644 src/core/crypto/encryption_layer.h create mode 100644 src/core/crypto/key_manager.cpp create mode 100644 src/core/crypto/key_manager.h create mode 100644 src/core/crypto/sha_util.cpp create mode 100644 src/core/crypto/sha_util.h create mode 100644 src/core/file_sys/card_image.cpp create mode 100644 src/core/file_sys/card_image.h create mode 100644 src/core/loader/xci.cpp create mode 100644 src/core/loader/xci.h diff --git a/.gitmodules b/.gitmodules index 0f94613c4f..247aac535d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,6 +25,9 @@ [submodule "unicorn"] path = externals/unicorn url = https://github.com/yuzu-emu/unicorn +[submodule "externals/mbedtls"] + path = externals/mbedtls + url = https://github.com/DarkLordZach/mbedtls [submodule "opus"] path = externals/opus url = https://github.com/ogniK5377/opus.git diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 4636761e51..987d0142ad 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -35,6 +35,10 @@ set(LZ4_BUNDLED_MODE ON) add_subdirectory(lz4/contrib/cmake_unofficial) target_include_directories(lz4_static INTERFACE ./lz4/lib) +# mbedtls +add_subdirectory(mbedtls) +target_include_directories(mbedtls PUBLIC ./mbedtls/include) + # MicroProfile add_library(microprofile INTERFACE) target_include_directories(microprofile INTERFACE ./microprofile) diff --git a/externals/mbedtls b/externals/mbedtls new file mode 160000 index 0000000000..adc11799ac --- /dev/null +++ b/externals/mbedtls @@ -0,0 +1 @@ +Subproject commit adc11799acb7a688b2715042b81eaf8644442bc4 diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index b8dd92b651..89004c3c04 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -736,6 +736,18 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { return paths[path]; } +std::string GetHactoolConfigurationPath() { +#ifdef _WIN32 + char path[MAX_PATH]; + if (SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path) != S_OK) + return ""; + std::string local_path = Common::StringFromFixedZeroTerminatedBuffer(path, MAX_PATH); + return local_path + "\\.switch"; +#else + return GetHomeDirectory() + "/.switch"; +#endif +} + size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size()); } diff --git a/src/common/file_util.h b/src/common/file_util.h index bc9272d89d..d530d86c9b 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -125,6 +125,8 @@ bool SetCurrentDir(const std::string& directory); // directory. To be used in "multi-user" mode (that is, installed). const std::string& GetUserPath(UserPath path, const std::string& new_path = ""); +std::string GetHactoolConfigurationPath(); + // Returns the path to where the sys file are std::string GetSysDirectory(); diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index d86c40d267..d7d2f9a20c 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -212,6 +212,7 @@ void FileBackend::Write(const Entry& entry) { CLS(Input) \ CLS(Network) \ CLS(Loader) \ + CLS(Crypto) \ CLS(WebService) // GetClassName is a macro defined by Windows.h, grrr... diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 140cd8e474..5a580b004c 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -97,6 +97,7 @@ enum class Class : ClassType { Audio_DSP, ///< The HLE implementation of the DSP Audio_Sink, ///< Emulator audio output backend Loader, ///< ROM loader + Crypto, ///< Cryptographic engine/functions Input, ///< Input emulation Network, ///< Network emulation WebService, ///< Interface to yuzu Web Services diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ccb0695e46..a69ee91465 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -12,6 +12,15 @@ add_library(core STATIC core_timing.h core_timing_util.cpp core_timing_util.h + crypto/aes_util.h + crypto/encryption_layer.cpp + crypto/encryption_layer.h + crypto/key_manager.cpp + crypto/key_manager.h + crypto/ctr_encryption_layer.cpp + crypto/ctr_encryption_layer.h + file_sys/card_image.cpp + file_sys/card_image.h file_sys/content_archive.cpp file_sys/content_archive.h file_sys/control_metadata.cpp @@ -317,6 +326,8 @@ add_library(core STATIC loader/nro.h loader/nso.cpp loader/nso.h + loader/xci.cpp + loader/xci.h memory.cpp memory.h memory_hook.cpp diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp new file mode 100644 index 0000000000..46326cdec4 --- /dev/null +++ b/src/core/crypto/aes_util.cpp @@ -0,0 +1,6 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +namespace Crypto { +} // namespace Crypto diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h new file mode 100644 index 0000000000..9807b92341 --- /dev/null +++ b/src/core/crypto/aes_util.h @@ -0,0 +1,118 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/assert.h" +#include "core/file_sys/vfs.h" +#include "mbedtls/cipher.h" + +namespace Crypto { + +enum class Mode { + CTR = MBEDTLS_CIPHER_AES_128_CTR, + ECB = MBEDTLS_CIPHER_AES_128_ECB, + XTS = MBEDTLS_CIPHER_AES_128_XTS, +}; + +enum class Op { + ENCRYPT, + DECRYPT, +}; + +template +struct AESCipher { + static_assert(std::is_same_v>, "Key must be std::array of u8."); + static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256."); + + AESCipher(Key key, Mode mode) { + mbedtls_cipher_init(&encryption_context); + mbedtls_cipher_init(&decryption_context); + + ASSERT_MSG((mbedtls_cipher_setup( + &encryption_context, + mbedtls_cipher_info_from_type(static_cast(mode))) || + mbedtls_cipher_setup(&decryption_context, + mbedtls_cipher_info_from_type( + static_cast(mode)))) == 0, + "Failed to initialize mbedtls ciphers."); + + ASSERT( + !mbedtls_cipher_setkey(&encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT)); + ASSERT( + !mbedtls_cipher_setkey(&decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT)); + //"Failed to set key on mbedtls ciphers."); + } + + ~AESCipher() { + mbedtls_cipher_free(&encryption_context); + mbedtls_cipher_free(&decryption_context); + } + + void SetIV(std::vector iv) { + ASSERT_MSG((mbedtls_cipher_set_iv(&encryption_context, iv.data(), iv.size()) || + mbedtls_cipher_set_iv(&decryption_context, iv.data(), iv.size())) == 0, + "Failed to set IV on mbedtls ciphers."); + } + + template + void Transcode(const Source* src, size_t size, Dest* dest, Op op) { + size_t written = 0; + + const auto context = op == Op::ENCRYPT ? &encryption_context : &decryption_context; + + mbedtls_cipher_reset(context); + + if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) { + mbedtls_cipher_update(context, reinterpret_cast(src), size, + reinterpret_cast(dest), &written); + if (written != size) + LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", + size, written); + } else { + const auto block_size = mbedtls_cipher_get_block_size(context); + + for (size_t offset = 0; offset < size; offset += block_size) { + auto length = std::min(block_size, size - offset); + mbedtls_cipher_update(context, reinterpret_cast(src) + offset, length, + reinterpret_cast(dest) + offset, &written); + if (written != length) + LOG_WARNING(Crypto, + "Not all data was decrypted requested={:016X}, actual={:016X}.", + length, written); + } + } + + mbedtls_cipher_finish(context, nullptr, nullptr); + } + + template + void XTSTranscode(const Source* src, size_t size, Dest* 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; + } + + for (size_t i = 0; i < size; i += sector_size) { + SetIV(CalculateNintendoTweak(sector_id++)); + Transcode(reinterpret_cast(src) + i, sector_size, + reinterpret_cast(dest) + i, op); + } + } + +private: + mbedtls_cipher_context_t encryption_context; + mbedtls_cipher_context_t decryption_context; + + static std::vector CalculateNintendoTweak(size_t sector_id) { + std::vector out(0x10); + for (size_t i = 0xF; i <= 0xF; --i) { + out[i] = sector_id & 0xFF; + sector_id >>= 8; + } + return out; + } +}; +} // namespace Crypto diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp new file mode 100644 index 0000000000..8799496e2f --- /dev/null +++ b/src/core/crypto/ctr_encryption_layer.cpp @@ -0,0 +1,56 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "core/crypto/ctr_encryption_layer.h" + +namespace Crypto { +CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset) + : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR), + iv(16, 0) {} + +size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { + if (length == 0) + return 0; + + const auto sector_offset = offset & 0xF; + 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; + } + + // offset does not fall on block boundary (0x10) + std::vector block = base->ReadBytes(0x10, offset - sector_offset); + UpdateIV(base_offset + offset - sector_offset); + cipher.Transcode(block.data(), block.size(), block.data(), Op::DECRYPT); + size_t read = 0x10 - sector_offset; + + if (length + sector_offset < 0x10) { + memcpy_s(data, length, block.data() + sector_offset, std::min(length, read)); + return read; + } + + memcpy_s(data, length, block.data() + sector_offset, read); + return read + Read(data + read, length - read, offset + read); +} + +void CTREncryptionLayer::SetIV(std::vector iv_) { + const auto length = std::min(iv_.size(), iv.size()); + for (size_t i = 0; i < length; ++i) + iv[i] = iv_[i]; +} + +void CTREncryptionLayer::UpdateIV(size_t offset) const { + offset >>= 4; + for (size_t i = 0; i < 8; ++i) { + iv[16 - i - 1] = offset & 0xFF; + offset >>= 8; + } + cipher.SetIV(iv); +} +} // namespace Crypto diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h new file mode 100644 index 0000000000..fe53e714b1 --- /dev/null +++ b/src/core/crypto/ctr_encryption_layer.h @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "aes_util.h" +#include "encryption_layer.h" +#include "key_manager.h" + +namespace Crypto { + +// Sits on top of a VirtualFile and provides CTR-mode AES decription. +struct CTREncryptionLayer : public EncryptionLayer { + CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset); + + size_t Read(u8* data, size_t length, size_t offset) const override; + + void SetIV(std::vector iv); + +private: + size_t base_offset; + + // Must be mutable as operations modify cipher contexts. + mutable AESCipher cipher; + mutable std::vector iv; + + void UpdateIV(size_t offset) const; +}; + +} // namespace Crypto diff --git a/src/core/crypto/encryption_layer.cpp b/src/core/crypto/encryption_layer.cpp new file mode 100644 index 0000000000..4e243051e2 --- /dev/null +++ b/src/core/crypto/encryption_layer.cpp @@ -0,0 +1,42 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/crypto/encryption_layer.h" + +namespace Crypto { + +EncryptionLayer::EncryptionLayer(FileSys::VirtualFile base_) : base(std::move(base_)) {} + +std::string EncryptionLayer::GetName() const { + return base->GetName(); +} + +size_t EncryptionLayer::GetSize() const { + return base->GetSize(); +} + +bool EncryptionLayer::Resize(size_t new_size) { + return false; +} + +std::shared_ptr EncryptionLayer::GetContainingDirectory() const { + return base->GetContainingDirectory(); +} + +bool EncryptionLayer::IsWritable() const { + return false; +} + +bool EncryptionLayer::IsReadable() const { + return true; +} + +size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) { + return 0; +} + +bool EncryptionLayer::Rename(std::string_view name) { + return base->Rename(name); +} +} // namespace Crypto diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h new file mode 100644 index 0000000000..2312870d83 --- /dev/null +++ b/src/core/crypto/encryption_layer.h @@ -0,0 +1,30 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "core/file_sys/vfs.h" + +namespace Crypto { + +// Basically non-functional class that implements all of the methods that are irrelevant to an +// EncryptionLayer. Reduces duplicate code. +struct EncryptionLayer : public FileSys::VfsFile { + explicit EncryptionLayer(FileSys::VirtualFile base); + + size_t Read(u8* data, size_t length, size_t offset) const override = 0; + + std::string GetName() const override; + size_t GetSize() const override; + bool Resize(size_t new_size) override; + std::shared_ptr GetContainingDirectory() const override; + bool IsWritable() const override; + bool IsReadable() const override; + size_t Write(const u8* data, size_t length, size_t offset) override; + bool Rename(std::string_view name) override; + +protected: + FileSys::VirtualFile base; +}; + +} // namespace Crypto diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp new file mode 100644 index 0000000000..05c6a70a24 --- /dev/null +++ b/src/core/crypto/key_manager.cpp @@ -0,0 +1,410 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/crypto/key_manager.h" +#include "mbedtls/sha256.h" + +namespace Crypto { +KeyManager keys = {}; + +std::unordered_map, SHA256Hash> KeyManager::s128_hash_prod = { + {{S128KeyType::MASTER, 0, 0}, + "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32}, + {{S128KeyType::MASTER, 1, 0}, + "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32}, + {{S128KeyType::MASTER, 2, 0}, + "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32}, + {{S128KeyType::MASTER, 3, 0}, + "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32}, + {{S128KeyType::MASTER, 4, 0}, + "75ff1d95d26113550ee6fcc20acb58e97edeb3a2ff52543ed5aec63bdcc3da50"_array32}, + {{S128KeyType::PACKAGE1, 0, 0}, + "4543CD1B7CAD7EE0466A3DE2086A0EF923805DCEA6C741541CDDB14F54F97B40"_array32}, + {{S128KeyType::PACKAGE1, 1, 0}, + "4A11DA019D26470C9B805F1721364830DC0096DD66EAC453B0D14455E5AF5CF8"_array32}, + {{S128KeyType::PACKAGE1, 2, 0}, + "CCA867360B3318246FBF0B8A86473176ED486DFE229772B941A02E84D50A3155"_array32}, + {{S128KeyType::PACKAGE1, 3, 0}, + "E65C383CDF526DFFAA77682868EBFA9535EE60D8075C961BBC1EDE5FBF7E3C5F"_array32}, + {{S128KeyType::PACKAGE1, 4, 0}, + "28ae73d6ae8f7206fca549e27097714e599df1208e57099416ff429b71370162"_array32}, + {{S128KeyType::PACKAGE2, 0, 0}, + "94D6F38B9D0456644E21DFF4707D092B70179B82D1AA2F5B6A76B8F9ED948264"_array32}, + {{S128KeyType::PACKAGE2, 1, 0}, + "7794F24FA879D378FEFDC8776B949B88AD89386410BE9025D463C619F1530509"_array32}, + {{S128KeyType::PACKAGE2, 2, 0}, + "5304BDDE6AC8E462961B5DB6E328B1816D245D36D6574BB78938B74D4418AF35"_array32}, + {{S128KeyType::PACKAGE2, 3, 0}, + "BE1E52C4345A979DDD4924375B91C902052C2E1CF8FBF2FAA42E8F26D5125B60"_array32}, + {{S128KeyType::PACKAGE2, 4, 0}, + "631b45d349ab8f76a050fe59512966fb8dbaf0755ef5b6903048bf036cfa611e"_array32}, + {{S128KeyType::TITLEKEK, 0, 0}, + "C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32}, + {{S128KeyType::TITLEKEK, 1, 0}, + "0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32}, + {{S128KeyType::TITLEKEK, 2, 0}, + "D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32}, + {{S128KeyType::TITLEKEK, 3, 0}, + "47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32}, + {{S128KeyType::TITLEKEK, 4, 0}, + "128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32}, + {{S128KeyType::ETICKET_RSA_KEK, 0, 0}, + "46cccf288286e31c931379de9efa288c95c9a15e40b00a4c563a8be244ece515"_array32}, + {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Application)}, + "592957F44FE5DB5EC6B095F568910E31A226D3B7FE42D64CFB9CE4051E90AEB6"_array32}, + {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Application)}, + "C2252A0FBF9D339ABC3D681351D00452F926E7CA0C6CA85F659078DE3FA647F3"_array32}, + {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Application)}, + "7C7722824B2F7C4938C40F3EA93E16CB69D3285EB133490EF8ECCD2C4B52DF41"_array32}, + {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Application)}, + "AFBB8EBFB2094F1CF71E330826AE06D64414FCA128C464618DF30EED92E62BE6"_array32}, + {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Application)}, + "5dc10eb81918da3f2fa90f69c8542511963656cfb31fb7c779581df8faf1f2f5"_array32}, + {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Ocean)}, + "AA2C65F0E27F730807A13F2ED5B99BE5183165B87C50B6ED48F5CAC2840687EB"_array32}, + {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Ocean)}, + "860185F2313A14F7006A029CB21A52750E7718C1E94FFB98C0AE2207D1A60165"_array32}, + {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Ocean)}, + "7283FB1EFBD42438DADF363FDB776ED355C98737A2AAE75D0E9283CE1C12A2E4"_array32}, + {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Ocean)}, + "9881C2D3AB70B14C8AA12016FC73ADAD93C6AD9FB59A9ECAD312B6F89E2413EC"_array32}, + {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Ocean)}, + "eaa6a8d242b89e174928fa9549a0f66ec1562e2576fac896f438a2b3c1fb6005"_array32}, + {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::System)}, + "194CF6BD14554DA8D457E14CBFE04E55C8FB8CA52E0AFB3D7CB7084AE435B801"_array32}, + {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::System)}, + "CE1DB7BB6E5962384889DB7A396AFD614F82F69DC38A33D2DEAF47F3E4B964B7"_array32}, + {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::System)}, + "42238DE5685DEF4FDE7BE42C0097CEB92447006386D6B5D5AAA2C9AFD2E28422"_array32}, + {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::System)}, + "1F6847F268E9D9C5D1AD4D7E226A63B833BF02071446957A962EF065521879C1"_array32}, + {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::System)}, + "644007f9913c3602399d4d75cc34faeb7f1faad18b23e34187b16fdc45f4980f"_array32}, +}; + +std::unordered_map, SHA256Hash> KeyManager::s256_hash_prod = { + {{S256KeyType::HEADER, 0, 0}, + "8E03DE24818D96CE4F2A09B43AF979E679974F7570713A61EED8B314864A11D5"_array32}, + {{S256KeyType::SD_SAVE, 0, 0}, + "13020ee72d0f8b8f9112dc738b829fdb017102499a7c2259b52aeefc0a273f5c"_array32}, + {{S256KeyType::SD_NCA, 0, 0}, + "8a1c05b4f88bae5b04d77f632e6acfc8893c4a05fd701f53585daafc996b532a"_array32}, +}; + +// TODO(DarkLordZach): Find missing hashes for dev keys. + +std::unordered_map, SHA256Hash> KeyManager::s128_hash_dev = { + {{S128KeyType::MASTER, 0, 0}, + "779dd8b533a2fb670f27b308cb8d0151c4a107568b817429172b7f80aa592c25"_array32}, + {{S128KeyType::MASTER, 1, 0}, + "0175c8bc49771576f75527be719098db4ebaf77707206749415663aa3a9ea9cc"_array32}, + {{S128KeyType::MASTER, 2, 0}, + "4f0b4d724e5a8787268157c7ce0767c26d2e2021832aa7020f306d6e260eea42"_array32}, + {{S128KeyType::MASTER, 3, 0}, + "7b5a29586c1f84f66fbfabb94518fc45408bb8e5445253d063dda7cfef2a818c"_array32}, + {{S128KeyType::MASTER, 4, 0}, + "87a61dbb05a8755de7fe069562aab38ebfb266c9eb835f09fa62dacc89c98341"_array32}, + {{S128KeyType::PACKAGE1, 0, 0}, + "166510bc63ae50391ebe4ee4ff90ca31cd0e2dd0ff6be839a2f573ec146cc23a"_array32}, + {{S128KeyType::PACKAGE1, 1, 0}, + "f74cd01b86743139c920ec54a8116c669eea805a0be1583e13fc5bc8de68645b"_array32}, + {{S128KeyType::PACKAGE1, 2, 0}, + "d0cdecd513bb6aa3d9dc6244c977dc8a5a7ea157d0a8747d79e7581146e1f768"_array32}, + {{S128KeyType::PACKAGE1, 3, 0}, + "aa39394d626b3b79f5b7ccc07378b5996b6d09bf0eb6771b0b40c9077fbfde8c"_array32}, + {{S128KeyType::PACKAGE1, 4, 0}, + "8f4754b8988c0e673fc2bbea0534cdd6075c815c9270754ae980aef3e4f0a508"_array32}, + {{S128KeyType::PACKAGE2, 0, 0}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::PACKAGE2, 1, 0}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::PACKAGE2, 2, 0}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::PACKAGE2, 3, 0}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::PACKAGE2, 4, 0}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::TITLEKEK, 0, 0}, + "C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32}, + {{S128KeyType::TITLEKEK, 1, 0}, + "0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32}, + {{S128KeyType::TITLEKEK, 2, 0}, + "D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32}, + {{S128KeyType::TITLEKEK, 3, 0}, + "47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32}, + {{S128KeyType::TITLEKEK, 4, 0}, + "128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32}, + {{S128KeyType::ETICKET_RSA_KEK, 0, 0}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Application)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Application)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Application)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Application)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Application)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Ocean)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Ocean)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Ocean)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Ocean)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Ocean)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::System)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::System)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::System)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::System)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::System)}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, +}; + +std::unordered_map, SHA256Hash> KeyManager::s256_hash_dev = { + {{S256KeyType::HEADER, 0, 0}, + "ecde86a76e37ac4fd7591d3aa55c00cc77d8595fc27968052ec18a177d939060"_array32}, + {{S256KeyType::SD_SAVE, 0, 0}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, + {{S256KeyType::SD_NCA, 0, 0}, + "0000000000000000000000000000000000000000000000000000000000000000"_array32}, +}; + +std::unordered_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}}, + {"master_key_03", {S128KeyType::MASTER, 3, 0}}, + {"master_key_04", {S128KeyType::MASTER, 4, 0}}, + {"package1_key_00", {S128KeyType::PACKAGE1, 0, 0}}, + {"package1_key_01", {S128KeyType::PACKAGE1, 1, 0}}, + {"package1_key_02", {S128KeyType::PACKAGE1, 2, 0}}, + {"package1_key_03", {S128KeyType::PACKAGE1, 3, 0}}, + {"package1_key_04", {S128KeyType::PACKAGE1, 4, 0}}, + {"package2_key_00", {S128KeyType::PACKAGE2, 0, 0}}, + {"package2_key_01", {S128KeyType::PACKAGE2, 1, 0}}, + {"package2_key_02", {S128KeyType::PACKAGE2, 2, 0}}, + {"package2_key_03", {S128KeyType::PACKAGE2, 3, 0}}, + {"package2_key_04", {S128KeyType::PACKAGE2, 4, 0}}, + {"titlekek_00", {S128KeyType::TITLEKEK, 0, 0}}, + {"titlekek_01", {S128KeyType::TITLEKEK, 1, 0}}, + {"titlekek_02", {S128KeyType::TITLEKEK, 2, 0}}, + {"titlekek_03", {S128KeyType::TITLEKEK, 3, 0}}, + {"titlekek_04", {S128KeyType::TITLEKEK, 4, 0}}, + {"eticket_rsa_kek", {S128KeyType::ETICKET_RSA_KEK, 0, 0}}, + {"key_area_key_application_00", + {S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Application)}}, + {"key_area_key_application_01", + {S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Application)}}, + {"key_area_key_application_02", + {S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Application)}}, + {"key_area_key_application_03", + {S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Application)}}, + {"key_area_key_application_04", + {S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Application)}}, + {"key_area_key_ocean_00", {S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Ocean)}}, + {"key_area_key_ocean_01", {S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Ocean)}}, + {"key_area_key_ocean_02", {S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Ocean)}}, + {"key_area_key_ocean_03", {S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Ocean)}}, + {"key_area_key_ocean_04", {S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Ocean)}}, + {"key_area_key_system_00", + {S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::System)}}, + {"key_area_key_system_01", + {S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::System)}}, + {"key_area_key_system_02", + {S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::System)}}, + {"key_area_key_system_03", + {S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::System)}}, + {"key_area_key_system_04", + {S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::System)}}, +}; + +std::unordered_map> KeyManager::s256_file_id = { + {"header_key", {S256KeyType::HEADER, 0, 0}}, + {"sd_card_save_key", {S256KeyType::SD_SAVE, 0, 0}}, + {"sd_card_nca_key", {S256KeyType::SD_NCA, 0, 0}}, +}; + +static u8 ToHexNibble(char c1) { + if (c1 >= 65 && c1 <= 70) + return c1 - 55; + if (c1 >= 97 && c1 <= 102) + return c1 - 87; + if (c1 >= 48 && c1 <= 57) + return c1 - 48; + throw std::logic_error("Invalid hex digit"); +} + +template +static std::array HexStringToArray(std::string_view str) { + std::array out{}; + for (size_t i = 0; i < 2 * Size; i += 2) { + auto d1 = str[i]; + auto d2 = str[i + 1]; + out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2); + } + return out; +} + +std::array operator""_array16(const char* str, size_t len) { + if (len != 32) + throw std::logic_error("Not of correct size."); + return HexStringToArray<16>(str); +} + +std::array operator""_array32(const char* str, size_t len) { + if (len != 64) + throw std::logic_error("Not of correct size."); + return HexStringToArray<32>(str); +} + +void KeyManager::SetValidationMode(bool dev) { + dev_mode = dev; +} + +void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { + const auto filename = std::string(filename_); + std::ifstream file(filename); + if (!file.is_open()) + return; + + std::string line; + while (std::getline(file, line)) { + std::vector out; + std::stringstream stream(line); + std::string item; + while (std::getline(stream, item, '=')) + out.push_back(std::move(item)); + + if (out.size() != 2) + continue; + + out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end()); + out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); + + if (is_title_keys) { + auto rights_id_raw = HexStringToArray<16>(out[0]); + u128 rights_id = *reinterpret_cast*>(&rights_id_raw); + Key128 key = HexStringToArray<16>(out[1]); + SetKey(S128KeyType::TITLEKEY, key, rights_id[1], rights_id[0]); + } 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[out[0]]; + Key128 key = HexStringToArray<16>(out[1]); + SetKey(index.type, key, index.field1, index.field2); + } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { + const auto index = s256_file_id[out[0]]; + Key256 key = HexStringToArray<32>(out[1]); + SetKey(index.type, key, index.field1, index.field2); + } + } + } +} + +bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) { + return s128_keys.find({id, field1, field2}) != s128_keys.end(); +} + +bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) { + return s256_keys.find({id, field1, field2}) != s256_keys.end(); +} + +Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) { + if (!HasKey(id, field1, field2)) + return {}; + return s128_keys[{id, field1, field2}]; +} + +Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) { + if (!HasKey(id, field1, field2)) + return {}; + return s256_keys[{id, field1, field2}]; +} + +void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { + s128_keys[{id, field1, field2}] = key; +} + +void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { + s256_keys[{id, field1, field2}] = key; +} + +bool KeyManager::ValidateKey(S128KeyType key, u64 field1, u64 field2) { + auto& hash = dev_mode ? s128_hash_dev : s128_hash_prod; + + KeyIndex id = {key, field1, field2}; + if (key == S128KeyType::SD_SEED || key == S128KeyType::TITLEKEY || + hash.find(id) == hash.end()) { + LOG_WARNING(Crypto, "Could not validate [{}]", id.DebugInfo()); + return true; + } + + if (!HasKey(key, field1, field2)) { + LOG_CRITICAL(Crypto, + "System has requested validation of [{}], but user has not added it. Add this " + "key to use functionality.", + id.DebugInfo()); + return false; + } + + SHA256Hash key_hash{}; + const auto a_key = GetKey(key, field1, field2); + mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0); + if (key_hash != hash[id]) { + LOG_CRITICAL(Crypto, + "The hash of the provided key for [{}] does not match the one on file. This " + "means you probably have an incorrect key. If you believe this to be in " + "error, contact the yuzu devs.", + id.DebugInfo()); + return false; + } + + return true; +} + +bool KeyManager::ValidateKey(S256KeyType key, u64 field1, u64 field2) { + auto& hash = dev_mode ? s256_hash_dev : s256_hash_prod; + + KeyIndex id = {key, field1, field2}; + if (hash.find(id) == hash.end()) { + LOG_ERROR(Crypto, "Could not validate [{}]", id.DebugInfo()); + return true; + } + + if (!HasKey(key, field1, field2)) { + LOG_ERROR(Crypto, + "System has requested validation of [{}], but user has not added it. Add this " + "key to use functionality.", + id.DebugInfo()); + return false; + } + + SHA256Hash key_hash{}; + const auto a_key = GetKey(key, field1, field2); + mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0); + if (key_hash != hash[id]) { + LOG_CRITICAL(Crypto, + "The hash of the provided key for [{}] does not match the one on file. This " + "means you probably have an incorrect key. If you believe this to be in " + "error, contact the yuzu devs.", + id.DebugInfo()); + return false; + } + + return true; +} +} // namespace Crypto diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h new file mode 100644 index 0000000000..155989e460 --- /dev/null +++ b/src/core/crypto/key_manager.h @@ -0,0 +1,116 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include +#include +#include +#include "common/common_types.h" + +namespace Crypto { + +typedef std::array Key128; +typedef std::array Key256; +typedef std::array SHA256Hash; + +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, // + SD_SAVE, // + SD_NCA, // +}; + +enum class S128KeyType : u64 { + MASTER, // f1=crypto revision + PACKAGE1, // f1=crypto revision + PACKAGE2, // f1=crypto revision + TITLEKEK, // f1=crypto revision + ETICKET_RSA_KEK, // + KEY_AREA, // f1=crypto revision f2=type {app, ocean, system} + SD_SEED, // + TITLEKEY, // f1=rights id LSB f2=rights id MSB +}; + +enum class KeyAreaKeyType : u8 { + Application, + Ocean, + System, +}; + +template +struct KeyIndex { + KeyType type; + u64 field1; + u64 field2; + + std::string DebugInfo() { + u8 key_size = 16; + if (std::is_same_v) + key_size = 32; + return fmt::format("key_size={:02X}, key={:02X}, field1={:016X}, field2={:016X}", key_size, + static_cast(type), field1, field2); + } +}; + +// The following two (== and hash) are so KeyIndex can be a key in unordered_map + +template +bool operator==(const KeyIndex& lhs, const KeyIndex& rhs) { + return lhs.type == rhs.type && lhs.field1 == rhs.field1 && lhs.field2 == rhs.field2; +} + +} // namespace Crypto + +namespace std { +template +struct hash> { + size_t operator()(const 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 Crypto { + +std::array operator"" _array16(const char* str, size_t len); +std::array operator"" _array32(const char* str, size_t len); + +struct KeyManager { + void SetValidationMode(bool dev); + void LoadFromFile(std::string_view filename, bool is_title_keys); + + bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0); + bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0); + + Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0); + Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0); + + void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); + void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); + + bool ValidateKey(S128KeyType key, u64 field1 = 0, u64 field2 = 0); + bool ValidateKey(S256KeyType key, u64 field1 = 0, u64 field2 = 0); + +private: + std::unordered_map, Key128> s128_keys; + std::unordered_map, Key256> s256_keys; + + bool dev_mode = false; + + static std::unordered_map, SHA256Hash> s128_hash_prod; + static std::unordered_map, SHA256Hash> s256_hash_prod; + static std::unordered_map, SHA256Hash> s128_hash_dev; + static std::unordered_map, SHA256Hash> s256_hash_dev; + static std::unordered_map> s128_file_id; + static std::unordered_map> s256_file_id; +}; + +extern KeyManager keys; + +} // namespace Crypto diff --git a/src/core/crypto/sha_util.cpp b/src/core/crypto/sha_util.cpp new file mode 100644 index 0000000000..180008a859 --- /dev/null +++ b/src/core/crypto/sha_util.cpp @@ -0,0 +1,5 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +namespace Crypto {} // namespace Crypto diff --git a/src/core/crypto/sha_util.h b/src/core/crypto/sha_util.h new file mode 100644 index 0000000000..fa3fa9d33e --- /dev/null +++ b/src/core/crypto/sha_util.h @@ -0,0 +1,20 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/assert.h" +#include "core/file_sys/vfs.h" +#include "key_manager.h" +#include "mbedtls/cipher.h" + +namespace Crypto { +typedef std::array SHA256Hash; + +inline SHA256Hash operator"" _HASH(const char* data, size_t len) { + if (len != 0x40) + return {}; +} + +} // namespace Crypto diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp new file mode 100644 index 0000000000..5ff09a362f --- /dev/null +++ b/src/core/file_sys/card_image.cpp @@ -0,0 +1,150 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "core/file_sys/card_image.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { + if (file->ReadObject(&header) != sizeof(GamecardHeader)) { + status = Loader::ResultStatus::ErrorInvalidFormat; + return; + } + + if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { + status = Loader::ResultStatus::ErrorInvalidFormat; + return; + } + + PartitionFilesystem main_hfs( + std::make_shared(file, header.hfs_size, header.hfs_offset)); + + if (main_hfs.GetStatus() != Loader::ResultStatus::Success) { + status = main_hfs.GetStatus(); + return; + } + + const static std::array partition_names = {"update", "normal", "secure", + "logo"}; + + for (XCIPartition partition : + {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { + auto raw = main_hfs.GetFile(partition_names[static_cast(partition)]); + if (raw != nullptr) + partitions[static_cast(partition)] = std::make_shared(raw); + } + + auto result = AddNCAFromPartition(XCIPartition::Secure); + if (result != Loader::ResultStatus::Success) { + status = result; + return; + } + result = AddNCAFromPartition(XCIPartition::Update); + if (result != Loader::ResultStatus::Success) { + status = result; + return; + } + + result = AddNCAFromPartition(XCIPartition::Normal); + if (result != Loader::ResultStatus::Success) { + status = result; + return; + } + + if (GetFormatVersion() >= 0x2) { + result = AddNCAFromPartition(XCIPartition::Logo); + if (result != Loader::ResultStatus::Success) { + status = result; + return; + } + } + + status = Loader::ResultStatus::Success; +} + +Loader::ResultStatus XCI::GetStatus() const { + return status; +} + +VirtualDir XCI::GetPartition(XCIPartition partition) const { + return partitions[static_cast(partition)]; +} + +VirtualDir XCI::GetSecurePartition() const { + return GetPartition(XCIPartition::Secure); +} + +VirtualDir XCI::GetNormalPartition() const { + return GetPartition(XCIPartition::Normal); +} + +VirtualDir XCI::GetUpdatePartition() const { + return GetPartition(XCIPartition::Update); +} + +VirtualDir XCI::GetLogoPartition() const { + return GetPartition(XCIPartition::Logo); +} + +std::shared_ptr XCI::GetNCAByType(NCAContentType type) const { + for (const auto& nca : ncas) { + if (nca->GetType() == type) + return nca; + } + + return nullptr; +} + +VirtualFile XCI::GetNCAFileByType(NCAContentType type) const { + auto nca = GetNCAByType(type); + if (nca != nullptr) + return nca->GetBaseFile(); + return nullptr; +} + +std::vector> XCI::GetFiles() const { + return {}; +} + +std::vector> XCI::GetSubdirectories() const { + return std::vector>(); +} + +std::string XCI::GetName() const { + return file->GetName(); +} + +std::shared_ptr XCI::GetParentDirectory() const { + return file->GetContainingDirectory(); +} + +bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { + return false; +} + +Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { + if (partitions[static_cast(part)] == nullptr) { + return Loader::ResultStatus::ErrorInvalidFormat; + } + + for (VirtualFile file : partitions[static_cast(part)]->GetFiles()) { + if (file->GetExtension() != "nca") + continue; + auto nca = std::make_shared(file); + if (nca->GetStatus() == Loader::ResultStatus::Success) + ncas.push_back(std::move(nca)); + } + + return Loader::ResultStatus::Success; +} + +u8 XCI::GetFormatVersion() const { + return GetLogoPartition() == nullptr ? 0x1 : 0x2; +} +} // namespace FileSys diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h new file mode 100644 index 0000000000..b765c8bc10 --- /dev/null +++ b/src/core/file_sys/card_image.h @@ -0,0 +1,93 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/swap.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { + +enum class GamecardSize : u8 { + S_1GB = 0xFA, + S_2GB = 0xF8, + S_4GB = 0xF0, + S_8GB = 0xE0, + S_16GB = 0xE1, + S_32GB = 0xE2, +}; + +struct GamecardInfo { + std::array data; +}; +static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size."); + +struct GamecardHeader { + std::array signature; + u32_le magic; + u32_le secure_area_start; + u32_le backup_area_start; + u8 kek_index; + GamecardSize size; + u8 header_version; + u8 flags; + u64_le package_id; + u64_le valid_data_end; + u128 info_iv; + u64_le hfs_offset; + u64_le hfs_size; + std::array hfs_header_hash; + std::array initial_data_hash; + u32_le secure_mode_flag; + u32_le title_key_flag; + u32_le key_flag; + u32_le normal_area_end; + GamecardInfo info; +}; +static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size."); + +enum class XCIPartition : u8 { Update, Normal, Secure, Logo }; + +class XCI : public ReadOnlyVfsDirectory { +public: + explicit XCI(VirtualFile file); + + Loader::ResultStatus GetStatus() const; + + u8 GetFormatVersion() const; + + VirtualDir GetPartition(XCIPartition partition) const; + VirtualDir GetSecurePartition() const; + VirtualDir GetNormalPartition() const; + VirtualDir GetUpdatePartition() const; + VirtualDir GetLogoPartition() const; + + std::shared_ptr GetNCAByType(NCAContentType type) const; + VirtualFile GetNCAFileByType(NCAContentType type) const; + + 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 AddNCAFromPartition(XCIPartition part); + + VirtualFile file; + GamecardHeader header{}; + + Loader::ResultStatus status; + + std::vector partitions; + std::vector> ncas; +}; +} // namespace FileSys diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 61cb0bbe30..add01974e2 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -6,9 +6,12 @@ #include #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 block_data; + std::array section_ctr; + INSERT_PADDING_BYTES(0xB8); +}; +static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); + struct PFS0Superblock { NCASectionHeaderBlock header_block; std::array 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) == 0xE8, "RomFSSuperblock has incorrect size."); +static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); + +union NCASectionHeader { + NCASectionRaw raw; + PFS0Superblock pfs0; + RomFSSuperblock romfs; +}; +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 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 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(type)); + + u128 out_128 = *reinterpret_cast(&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( + std::move(in), GetKeyAreaKey(NCASectionCryptoType::CTR), starting_offset); + std::vector 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(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 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 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 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(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(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(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( - std::make_shared(file, size, offset)); + Decrypt(section, std::make_shared(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; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 0b8b9db612..1e8d9c8aed 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -12,10 +12,10 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/crypto/key_manager.h" #include "core/file_sys/partition_filesystem.h" namespace FileSys { - enum class NCAContentType : u8 { Program = 0, Meta = 1, @@ -24,6 +24,13 @@ enum class NCAContentType : u8 { Data = 4, }; +enum class NCASectionCryptoType : u8 { + NONE = 1, + XTS = 2, + CTR = 3, + BKTR = 4, +}; + struct NCASectionTableEntry { u32_le media_offset; u32_le media_end_offset; @@ -48,20 +55,19 @@ struct NCAHeader { std::array rights_id; std::array section_tables; std::array, 0x4> hash_tables; - std::array, 0x4> key_area; + std::array key_area; INSERT_PADDING_BYTES(0xC0); }; static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); +union NCASectionHeader; + inline bool IsDirectoryExeFS(const std::shared_ptr& pfs) { // According to switchbrew, an exefs must only contain these two files: return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; } -inline bool IsValidNCA(const NCAHeader& header) { - return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || - header.magic == Common::MakeMagic('N', 'C', 'A', '3'); -} +bool IsValidNCA(const NCAHeader& header); // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. // After construction, use GetStatus to determine if the file is valid and ready to be used. @@ -81,6 +87,8 @@ public: VirtualFile GetRomFS() const; VirtualDir GetExeFS() const; + VirtualFile GetBaseFile() const; + protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; @@ -95,6 +103,12 @@ private: NCAHeader header{}; Loader::ResultStatus status{}; + + bool encrypted; + + Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type); + + VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, size_t starting_offset); }; } // namespace FileSys diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 84a6a73978..57d0aeb854 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -285,6 +285,27 @@ bool ReadOnlyVfsDirectory::Rename(std::string_view name) { return false; } +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size) { + if (file1->GetSize() != file2->GetSize()) + return false; + + std::vector f1_v(block_size); + std::vector f2_v(block_size); + for (size_t i = 0; i < file1->GetSize(); i += block_size) { + auto f1_vs = file1->Read(f1_v.data(), block_size, i); + auto f2_vs = file2->Read(f2_v.data(), block_size, i); + + if (f1_vs != f2_vs) + return false; + for (size_t j = 0; j < f1_vs; ++j) { + if (f1_v[j] != f2_v[j]) + return false; + } + } + + return true; +} + bool VfsRawCopy(VirtualFile src, VirtualFile dest) { if (src == nullptr || dest == nullptr) return false; diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index cf871edd67..fab9e2b45f 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -245,6 +245,9 @@ struct ReadOnlyVfsDirectory : public VfsDirectory { bool Rename(std::string_view name) override; }; +// Compare the two files, byte-for-byte, in increments specificed by block_size +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x200); + // A method that copies the raw data between two different implementations of VirtualFile. If you // are using the same implementation, it is probably better to use the Copy method in the parent // directory of src/dest. diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index cbc4177c61..57e6c03653 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -13,6 +13,7 @@ #include "core/loader/nca.h" #include "core/loader/nro.h" #include "core/loader/nso.h" +#include "core/loader/xci.h" namespace Loader { @@ -35,6 +36,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) { CHECK_TYPE(NSO) CHECK_TYPE(NRO) CHECK_TYPE(NCA) + CHECK_TYPE(XCI) #undef CHECK_TYPE @@ -60,6 +62,8 @@ FileType GuessFromFilename(const std::string& name) { return FileType::NSO; if (extension == "nca") return FileType::NCA; + if (extension == "xci") + return FileType::XCI; return FileType::Unknown; } @@ -74,6 +78,8 @@ const char* GetFileTypeString(FileType type) { return "NSO"; case FileType::NCA: return "NCA"; + case FileType::XCI: + return "XCI"; case FileType::DeconstructedRomDirectory: return "Directory"; case FileType::Error: @@ -111,6 +117,9 @@ static std::unique_ptr GetFileLoader(FileSys::VirtualFile file, FileT case FileType::NCA: return std::make_unique(std::move(file)); + case FileType::XCI: + return std::make_unique(std::move(file)); + // NX deconstructed ROM directory. case FileType::DeconstructedRomDirectory: return std::make_unique(std::move(file)); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index fbf11e5d0f..dc8d283114 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -31,6 +31,7 @@ enum class FileType { NSO, NRO, NCA, + XCI, DeconstructedRomDirectory, }; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index c80df23be0..a1f8235d12 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -25,12 +25,10 @@ namespace Loader { AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) { - // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support. - FileSys::NCAHeader header{}; - if (sizeof(FileSys::NCAHeader) != file->ReadObject(&header)) - return FileType::Error; + FileSys::NCA nca(file); - if (IsValidNCA(header) && header.content_type == FileSys::NCAContentType::Program) + if (nca.GetStatus() == ResultStatus::Success && + nca.GetType() == FileSys::NCAContentType::Program) return FileType::NCA; return FileType::Error; @@ -98,12 +96,21 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr& process) { } ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { - if (nca == nullptr || nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0) + if (nca == nullptr) + return ResultStatus::ErrorNotLoaded; + if (nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0) return ResultStatus::ErrorNotUsed; dir = nca->GetRomFS(); return ResultStatus::Success; } +ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { + if (nca == nullptr) + return ResultStatus::ErrorNotLoaded; + out_program_id = nca->GetTitleId(); + return ResultStatus::Success; +} + AppLoader_NCA::~AppLoader_NCA() = default; } // namespace Loader diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 52c95953ac..fb96c0a22e 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -33,6 +33,8 @@ public: ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; + ResultStatus ReadProgramId(u64& out_program_id) override; + ~AppLoader_NCA(); private: diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp new file mode 100644 index 0000000000..9759e33d1e --- /dev/null +++ b/src/core/loader/xci.cpp @@ -0,0 +1,67 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/program_metadata.h" +#include "core/file_sys/romfs.h" +#include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/nso.h" +#include "core/loader/xci.h" +#include "core/memory.h" + +namespace Loader { + +AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) + : AppLoader(file), xci(std::make_unique(file)), + nca_loader(std::make_unique( + xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {} + +FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) { + FileSys::XCI xci(file); + + if (xci.GetStatus() == ResultStatus::Success && + xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr && + AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) == + FileType::NCA) + return FileType::XCI; + + return FileType::Error; +} + +ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr& process) { + if (is_loaded) { + return ResultStatus::ErrorAlreadyLoaded; + } + + auto result = nca_loader->Load(process); + if (result != ResultStatus::Success) + return result; + + is_loaded = true; + + return ResultStatus::Success; +} + +ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) { + return nca_loader->ReadRomFS(dir); +} + +ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) { + return nca_loader->ReadProgramId(out_program_id); +} + +AppLoader_XCI::~AppLoader_XCI() = default; + +} // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h new file mode 100644 index 0000000000..a9cee1ca35 --- /dev/null +++ b/src/core/loader/xci.h @@ -0,0 +1,42 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/card_image.h" +#include "core/loader/nca.h" + +namespace Loader { + +/// Loads an XCI file +class AppLoader_XCI final : public AppLoader { +public: + explicit AppLoader_XCI(FileSys::VirtualFile file); + + /** + * 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; + + ~AppLoader_XCI(); + +private: + FileSys::ProgramMetadata metadata; + + std::unique_ptr xci; + std::unique_ptr nca_loader; +}; + +} // namespace Loader diff --git a/src/core/settings.h b/src/core/settings.h index 8cc65e4347..55da39b6cf 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -137,6 +137,8 @@ struct Values { std::string log_filter; + bool use_dev_keys; + // Audio std::string sink_id; std::string audio_device_id; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index e8b3a9866a..2f2d8c4c5e 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -109,6 +109,7 @@ void Config::ReadValues() { qt_config->beginGroup("Miscellaneous"); Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); + Settings::values.use_dev_keys = qt_config->value("use_dev_keys", false).toBool(); qt_config->endGroup(); qt_config->beginGroup("Debugging"); @@ -218,6 +219,7 @@ void Config::SaveValues() { qt_config->beginGroup("Miscellaneous"); qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); + qt_config->setValue("use_dev_keys", Settings::values.use_dev_keys); qt_config->endGroup(); qt_config->beginGroup("Debugging"); diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 99e6634a15..71e24a9e22 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -365,7 +365,7 @@ void GameList::LoadInterfaceLayout() { item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); } -const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca"}; +const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"}; static bool HasSupportedFileExtension(const std::string& file_name) { QFileInfo file = QFileInfo(file_name.c_str()); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index be38cfa9bc..d3523e6ac2 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "common/common_paths.h" #include "common/logging/backend.h" #include "common/logging/filter.h" @@ -88,6 +89,19 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { ui.setupUi(this); statusBar()->hide(); + // Initialize keys + std::string keys_dir = FileUtil::GetHactoolConfigurationPath(); + if (Settings::values.use_dev_keys) { + Crypto::keys.SetValidationMode(true); + if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys")) + Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false); + } else { + if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys")) + Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false); + } + if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys")) + Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "title.keys", true); + default_theme_paths = QIcon::themeSearchPaths(); UpdateUITheme(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index c581e96997..9bf26717fe 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -119,6 +119,7 @@ void Config::ReadValues() { // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); + Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); // Debugging Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index b5392c499b..955e2ba140 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -23,6 +23,7 @@ #include "yuzu_cmd/emu_window/emu_window_sdl2.h" #include +#include "core/crypto/key_manager.h" #ifndef _MSC_VER #include #endif @@ -71,6 +72,20 @@ static void InitializeLogging() { /// Application entry point int main(int argc, char** argv) { Config config; + + // Initialize keys + std::string keys_dir = FileUtil::GetHactoolConfigurationPath(); + if (Settings::values.use_dev_keys) { + Crypto::keys.SetValidationMode(true); + if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys")) + Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false); + } else { + if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys")) + Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false); + } + if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys")) + Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "title.keys", true); + int option_index = 0; bool use_gdbstub = Settings::values.use_gdbstub; u32 gdb_port = static_cast(Settings::values.gdbstub_port); From c54a10cb4f9912aa0827b8a1b007757252fc90ae Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 28 Jul 2018 14:28:14 -0400 Subject: [PATCH 02/13] Update mbedtls and fix compile error --- externals/mbedtls | 2 +- src/core/crypto/key_manager.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/externals/mbedtls b/externals/mbedtls index adc11799ac..06442b840e 160000 --- a/externals/mbedtls +++ b/externals/mbedtls @@ -1 +1 @@ -Subproject commit adc11799acb7a688b2715042b81eaf8644442bc4 +Subproject commit 06442b840ea62e2ceda0dc58f255cfff49fed5b6 diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 155989e460..b892a83f2c 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "common/common_types.h" namespace Crypto { From 83c3ae8be87463486239246a93f9dadf5d2b27bd Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 28 Jul 2018 14:42:16 -0400 Subject: [PATCH 03/13] Add missing string.h include --- src/core/crypto/ctr_encryption_layer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp index 8799496e2f..6a0d396ed1 100644 --- a/src/core/crypto/ctr_encryption_layer.cpp +++ b/src/core/crypto/ctr_encryption_layer.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "common/assert.h" #include "core/crypto/ctr_encryption_layer.h" From 22342487e8fb851a9837db22408db56240aa6931 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 28 Jul 2018 16:23:00 -0400 Subject: [PATCH 04/13] Extract mbedtls to cpp file --- src/common/file_util.cpp | 2 +- src/core/CMakeLists.txt | 1 + src/core/crypto/aes_util.cpp | 102 ++++++++++++++++++++++++++- src/core/crypto/aes_util.h | 104 ++++++---------------------- src/core/file_sys/content_archive.h | 3 +- 5 files changed, 126 insertions(+), 86 deletions(-) diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 89004c3c04..a26702f54c 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -739,7 +739,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { std::string GetHactoolConfigurationPath() { #ifdef _WIN32 char path[MAX_PATH]; - if (SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path) != S_OK) + if (SHGetFolderPathA(nullptr, CSIDL_PROFILE, nullptr, 0, path) != S_OK) return ""; std::string local_path = Common::StringFromFixedZeroTerminatedBuffer(path, MAX_PATH); return local_path + "\\.switch"; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a69ee91465..528d96a58b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(core STATIC core_timing.h core_timing_util.cpp core_timing_util.h + crypto/aes_util.cpp crypto/aes_util.h crypto/encryption_layer.cpp crypto/encryption_layer.h diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp index 46326cdec4..a9646e52f3 100644 --- a/src/core/crypto/aes_util.cpp +++ b/src/core/crypto/aes_util.cpp @@ -2,5 +2,103 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -namespace Crypto { -} // namespace Crypto +#include "core/crypto/aes_util.h" +#include "mbedtls/cipher.h" + +namespace Core::Crypto { +static_assert(static_cast(Mode::CTR) == static_cast(MBEDTLS_CIPHER_AES_128_CTR), "CTR mode is incorrect."); +static_assert(static_cast(Mode::ECB) == static_cast(MBEDTLS_CIPHER_AES_128_ECB), "ECB mode is incorrect."); +static_assert(static_cast(Mode::XTS) == static_cast(MBEDTLS_CIPHER_AES_128_XTS), "XTS mode is incorrect."); + +template +Crypto::AESCipher::AESCipher(Key key, Mode mode) { + mbedtls_cipher_init(encryption_context.get()); + mbedtls_cipher_init(decryption_context.get()); + + ASSERT_MSG((mbedtls_cipher_setup( + encryption_context.get(), + mbedtls_cipher_info_from_type(static_cast(mode))) || + mbedtls_cipher_setup(decryption_context.get(), + mbedtls_cipher_info_from_type( + static_cast(mode)))) == 0, + "Failed to initialize mbedtls ciphers."); + + ASSERT( + !mbedtls_cipher_setkey(encryption_context.get(), key.data(), KeySize * 8, MBEDTLS_ENCRYPT)); + ASSERT( + !mbedtls_cipher_setkey(decryption_context.get(), key.data(), KeySize * 8, MBEDTLS_DECRYPT)); + //"Failed to set key on mbedtls ciphers."); +} + +template +AESCipher::~AESCipher() { + mbedtls_cipher_free(encryption_context.get()); + mbedtls_cipher_free(decryption_context.get()); +} + +template +void AESCipher::SetIV(std::vector iv) { + ASSERT_MSG((mbedtls_cipher_set_iv(encryption_context.get(), iv.data(), iv.size()) || + mbedtls_cipher_set_iv(decryption_context.get(), iv.data(), iv.size())) == 0, + "Failed to set IV on mbedtls ciphers."); +} + +template +void AESCipher::Transcode(const u8* src, size_t size, u8* dest, Op op) { + size_t written = 0; + + const auto context = op == Op::Encrypt ? encryption_context.get() : decryption_context.get(); + + mbedtls_cipher_reset(context); + + if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) { + mbedtls_cipher_update(context, src, size, + dest, &written); + if (written != size) + LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", + size, written); + } else { + const auto block_size = mbedtls_cipher_get_block_size(context); + + for (size_t offset = 0; offset < size; offset += block_size) { + auto length = std::min(block_size, size - offset); + mbedtls_cipher_update(context, src + offset, length, + dest + offset, &written); + if (written != length) + LOG_WARNING(Crypto, + "Not all data was decrypted requested={:016X}, actual={:016X}.", + length, written); + } + } + + mbedtls_cipher_finish(context, nullptr, nullptr); +} + +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; + } + + for (size_t i = 0; i < size; i += sector_size) { + SetIV(CalculateNintendoTweak(sector_id++)); + Transcode(src + i, sector_size, + dest + i, op); + } +} + +template +std::vector AESCipher::CalculateNintendoTweak(size_t sector_id) { + std::vector out(0x10); + for (size_t i = 0xF; i <= 0xF; --i) { + out[i] = sector_id & 0xFF; + sector_id >>= 8; + } + return out; +} + +template class AESCipher; +template class AESCipher; +} \ No newline at end of file diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h index 9807b92341..5c09718b22 100644 --- a/src/core/crypto/aes_util.h +++ b/src/core/crypto/aes_util.h @@ -6,113 +6,53 @@ #include "common/assert.h" #include "core/file_sys/vfs.h" -#include "mbedtls/cipher.h" -namespace Crypto { +namespace Core::Crypto { enum class Mode { - CTR = MBEDTLS_CIPHER_AES_128_CTR, - ECB = MBEDTLS_CIPHER_AES_128_ECB, - XTS = MBEDTLS_CIPHER_AES_128_XTS, + CTR = 11, + ECB = 2, + XTS = 70, }; enum class Op { - ENCRYPT, - DECRYPT, + Encrypt, + Decrypt, }; +struct mbedtls_cipher_context_t; + template -struct AESCipher { +class AESCipher { static_assert(std::is_same_v>, "Key must be std::array of u8."); static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256."); - AESCipher(Key key, Mode mode) { - mbedtls_cipher_init(&encryption_context); - mbedtls_cipher_init(&decryption_context); +public: + AESCipher(Key key, Mode mode); - ASSERT_MSG((mbedtls_cipher_setup( - &encryption_context, - mbedtls_cipher_info_from_type(static_cast(mode))) || - mbedtls_cipher_setup(&decryption_context, - mbedtls_cipher_info_from_type( - static_cast(mode)))) == 0, - "Failed to initialize mbedtls ciphers."); + ~AESCipher(); - ASSERT( - !mbedtls_cipher_setkey(&encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT)); - ASSERT( - !mbedtls_cipher_setkey(&decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT)); - //"Failed to set key on mbedtls ciphers."); - } - - ~AESCipher() { - mbedtls_cipher_free(&encryption_context); - mbedtls_cipher_free(&decryption_context); - } - - void SetIV(std::vector iv) { - ASSERT_MSG((mbedtls_cipher_set_iv(&encryption_context, iv.data(), iv.size()) || - mbedtls_cipher_set_iv(&decryption_context, iv.data(), iv.size())) == 0, - "Failed to set IV on mbedtls ciphers."); - } + void SetIV(std::vector iv); template void Transcode(const Source* src, size_t size, Dest* dest, Op op) { - size_t written = 0; - - const auto context = op == Op::ENCRYPT ? &encryption_context : &decryption_context; - - mbedtls_cipher_reset(context); - - if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) { - mbedtls_cipher_update(context, reinterpret_cast(src), size, - reinterpret_cast(dest), &written); - if (written != size) - LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", - size, written); - } else { - const auto block_size = mbedtls_cipher_get_block_size(context); - - for (size_t offset = 0; offset < size; offset += block_size) { - auto length = std::min(block_size, size - offset); - mbedtls_cipher_update(context, reinterpret_cast(src) + offset, length, - reinterpret_cast(dest) + offset, &written); - if (written != length) - LOG_WARNING(Crypto, - "Not all data was decrypted requested={:016X}, actual={:016X}.", - length, written); - } - } - - mbedtls_cipher_finish(context, nullptr, nullptr); + Transcode(reinterpret_cast(src), size, reinterpret_cast(dest), op); } + void Transcode(const u8* src, size_t size, u8* dest, Op op); + template void XTSTranscode(const Source* src, size_t size, Dest* 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; - } - - for (size_t i = 0; i < size; i += sector_size) { - SetIV(CalculateNintendoTweak(sector_id++)); - Transcode(reinterpret_cast(src) + i, sector_size, - reinterpret_cast(dest) + i, op); - } + XTSTranscode(reinterpret_cast(src), size, reinterpret_cast(dest), sector_id, sector_size, op); } + void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size, Op op); + private: - mbedtls_cipher_context_t encryption_context; - mbedtls_cipher_context_t decryption_context; + std::unique_ptr encryption_context; + std::unique_ptr decryption_context; - static std::vector CalculateNintendoTweak(size_t sector_id) { - std::vector out(0x10); - for (size_t i = 0xF; i <= 0xF; --i) { - out[i] = sector_id & 0xFF; - sector_id >>= 8; - } - return out; - } + static std::vector CalculateNintendoTweak(size_t sector_id); }; } // namespace Crypto diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 1e8d9c8aed..d9ad3bf7ed 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -9,6 +9,7 @@ #include #include +#include "core/loader/loader.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -108,7 +109,7 @@ private: Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type); - VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, size_t starting_offset); + VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset); }; } // namespace FileSys From 239a3113e4c6a53a2c7b12e67a0f21afae24b0aa Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 28 Jul 2018 21:39:42 -0400 Subject: [PATCH 05/13] Make XCI comply to review and style guidelines --- src/core/crypto/aes_util.cpp | 82 ++--- src/core/crypto/aes_util.h | 13 +- src/core/crypto/ctr_encryption_layer.cpp | 20 +- src/core/crypto/ctr_encryption_layer.h | 15 +- src/core/crypto/encryption_layer.cpp | 4 +- src/core/crypto/encryption_layer.h | 5 +- src/core/crypto/key_manager.cpp | 376 +++++------------------ src/core/crypto/key_manager.h | 77 +++-- src/core/file_sys/card_image.cpp | 15 +- src/core/file_sys/content_archive.cpp | 48 ++- src/core/file_sys/content_archive.h | 5 +- src/core/file_sys/vfs.cpp | 7 +- src/core/loader/xci.cpp | 7 +- src/core/loader/xci.h | 3 +- src/yuzu/main.cpp | 15 +- src/yuzu_cmd/yuzu.cpp | 13 - 16 files changed, 223 insertions(+), 482 deletions(-) diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp index a9646e52f3..4690af5f83 100644 --- a/src/core/crypto/aes_util.cpp +++ b/src/core/crypto/aes_util.cpp @@ -2,58 +2,69 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "core/crypto/aes_util.h" -#include "mbedtls/cipher.h" +#include "core/crypto/key_manager.h" namespace Core::Crypto { -static_assert(static_cast(Mode::CTR) == static_cast(MBEDTLS_CIPHER_AES_128_CTR), "CTR mode is incorrect."); -static_assert(static_cast(Mode::ECB) == static_cast(MBEDTLS_CIPHER_AES_128_ECB), "ECB mode is incorrect."); -static_assert(static_cast(Mode::XTS) == static_cast(MBEDTLS_CIPHER_AES_128_XTS), "XTS mode is incorrect."); -template -Crypto::AESCipher::AESCipher(Key key, Mode mode) { - mbedtls_cipher_init(encryption_context.get()); - mbedtls_cipher_init(decryption_context.get()); +static_assert(static_cast(Mode::CTR) == static_cast(MBEDTLS_CIPHER_AES_128_CTR), + "CTR has incorrect value."); +static_assert(static_cast(Mode::ECB) == static_cast(MBEDTLS_CIPHER_AES_128_ECB), + "ECB has incorrect value."); +static_assert(static_cast(Mode::XTS) == static_cast(MBEDTLS_CIPHER_AES_128_XTS), + "XTS has incorrect value."); + +// Structure to hide mbedtls types from header file +struct CipherContext { + mbedtls_cipher_context_t encryption_context; + mbedtls_cipher_context_t decryption_context; +}; + +template +Crypto::AESCipher::AESCipher(Key key, Mode mode) + : ctx(std::make_unique()) { + mbedtls_cipher_init(&ctx->encryption_context); + mbedtls_cipher_init(&ctx->decryption_context); ASSERT_MSG((mbedtls_cipher_setup( - encryption_context.get(), - mbedtls_cipher_info_from_type(static_cast(mode))) || - mbedtls_cipher_setup(decryption_context.get(), - mbedtls_cipher_info_from_type( - static_cast(mode)))) == 0, + &ctx->encryption_context, + mbedtls_cipher_info_from_type(static_cast(mode))) || + mbedtls_cipher_setup( + &ctx->decryption_context, + mbedtls_cipher_info_from_type(static_cast(mode)))) == 0, "Failed to initialize mbedtls ciphers."); ASSERT( - !mbedtls_cipher_setkey(encryption_context.get(), key.data(), KeySize * 8, MBEDTLS_ENCRYPT)); + !mbedtls_cipher_setkey(&ctx->encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT)); ASSERT( - !mbedtls_cipher_setkey(decryption_context.get(), key.data(), KeySize * 8, MBEDTLS_DECRYPT)); + !mbedtls_cipher_setkey(&ctx->decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT)); //"Failed to set key on mbedtls ciphers."); } -template +template AESCipher::~AESCipher() { - mbedtls_cipher_free(encryption_context.get()); - mbedtls_cipher_free(decryption_context.get()); + mbedtls_cipher_free(&ctx->encryption_context); + mbedtls_cipher_free(&ctx->decryption_context); } -template +template void AESCipher::SetIV(std::vector iv) { - ASSERT_MSG((mbedtls_cipher_set_iv(encryption_context.get(), iv.data(), iv.size()) || - mbedtls_cipher_set_iv(decryption_context.get(), iv.data(), iv.size())) == 0, + ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, iv.data(), iv.size()) || + mbedtls_cipher_set_iv(&ctx->decryption_context, iv.data(), iv.size())) == 0, "Failed to set IV on mbedtls ciphers."); } -template -void AESCipher::Transcode(const u8* src, size_t size, u8* dest, Op op) { +template +void AESCipher::Transcode(const u8* src, size_t size, u8* dest, Op op) { size_t written = 0; - const auto context = op == Op::Encrypt ? encryption_context.get() : decryption_context.get(); + const auto context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context; mbedtls_cipher_reset(context); if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) { - mbedtls_cipher_update(context, src, size, - dest, &written); + mbedtls_cipher_update(context, src, size, dest, &written); if (written != size) LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", size, written); @@ -62,11 +73,9 @@ void AESCipher::Transcode(const u8* src, size_t size, u8* dest, Op for (size_t offset = 0; offset < size; offset += block_size) { auto length = std::min(block_size, size - offset); - mbedtls_cipher_update(context, src + offset, length, - dest + offset, &written); + mbedtls_cipher_update(context, src + offset, length, dest + offset, &written); if (written != length) - LOG_WARNING(Crypto, - "Not all data was decrypted requested={:016X}, actual={:016X}.", + LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", length, written); } } @@ -74,9 +83,9 @@ void AESCipher::Transcode(const u8* src, size_t size, u8* dest, Op mbedtls_cipher_finish(context, nullptr, nullptr); } -template -void AESCipher::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size, - Op 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; @@ -84,12 +93,11 @@ void AESCipher::XTSTranscode(const u8* src, size_t size, u8* dest, for (size_t i = 0; i < size; i += sector_size) { SetIV(CalculateNintendoTweak(sector_id++)); - Transcode(src + i, sector_size, - dest + i, op); + Transcode(src + i, sector_size, dest + i, op); } } -template +template std::vector AESCipher::CalculateNintendoTweak(size_t sector_id) { std::vector out(0x10); for (size_t i = 0xF; i <= 0xF; --i) { @@ -101,4 +109,4 @@ std::vector AESCipher::CalculateNintendoTweak(size_t sector_id template class AESCipher; template class AESCipher; -} \ No newline at end of file +} // namespace Core::Crypto \ No newline at end of file diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h index 5c09718b22..fa77d55601 100644 --- a/src/core/crypto/aes_util.h +++ b/src/core/crypto/aes_util.h @@ -20,7 +20,7 @@ enum class Op { Decrypt, }; -struct mbedtls_cipher_context_t; +struct CipherContext; template class AESCipher { @@ -44,15 +44,16 @@ public: template void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id, size_t sector_size, Op op) { - XTSTranscode(reinterpret_cast(src), size, reinterpret_cast(dest), sector_id, sector_size, op); + XTSTranscode(reinterpret_cast(src), size, reinterpret_cast(dest), sector_id, + sector_size, op); } - void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size, Op op); + void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size, + Op op); private: - std::unique_ptr encryption_context; - std::unique_ptr decryption_context; + std::unique_ptr ctx; static std::vector CalculateNintendoTweak(size_t sector_id); }; -} // namespace Crypto +} // namespace Core::Crypto diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp index 6a0d396ed1..5dbc257e5e 100644 --- a/src/core/crypto/ctr_encryption_layer.cpp +++ b/src/core/crypto/ctr_encryption_layer.cpp @@ -6,7 +6,8 @@ #include "common/assert.h" #include "core/crypto/ctr_encryption_layer.h" -namespace Crypto { +namespace Core::Crypto { + CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset) : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR), iv(16, 0) {} @@ -21,29 +22,28 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { 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); + cipher.Transcode(raw.data(), length, data, Op::Decrypt); return length; } // offset does not fall on block boundary (0x10) std::vector block = base->ReadBytes(0x10, offset - sector_offset); UpdateIV(base_offset + offset - sector_offset); - cipher.Transcode(block.data(), block.size(), block.data(), Op::DECRYPT); + cipher.Transcode(block.data(), block.size(), block.data(), Op::Decrypt); size_t read = 0x10 - sector_offset; if (length + sector_offset < 0x10) { - memcpy_s(data, length, block.data() + sector_offset, std::min(length, read)); + memcpy(data, block.data() + sector_offset, std::min(length, read)); return read; } - memcpy_s(data, length, block.data() + sector_offset, read); + memcpy(data, block.data() + sector_offset, read); return read + Read(data + read, length - read, offset + read); } -void CTREncryptionLayer::SetIV(std::vector iv_) { - const auto length = std::min(iv_.size(), iv.size()); - for (size_t i = 0; i < length; ++i) - iv[i] = iv_[i]; +void CTREncryptionLayer::SetIV(const std::vector& iv_) { + const auto length = std::min(iv_.size(), iv.size()); + iv.assign(iv_.cbegin(), iv_.cbegin() + length); } void CTREncryptionLayer::UpdateIV(size_t offset) const { @@ -54,4 +54,4 @@ void CTREncryptionLayer::UpdateIV(size_t offset) const { } cipher.SetIV(iv); } -} // namespace Crypto +} // namespace Core::Crypto diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h index fe53e714b1..697d7c6a56 100644 --- a/src/core/crypto/ctr_encryption_layer.h +++ b/src/core/crypto/ctr_encryption_layer.h @@ -4,19 +4,20 @@ #pragma once -#include "aes_util.h" -#include "encryption_layer.h" -#include "key_manager.h" +#include "core/crypto/aes_util.h" +#include "core/crypto/encryption_layer.h" +#include "core/crypto/key_manager.h" -namespace Crypto { +namespace Core::Crypto { // Sits on top of a VirtualFile and provides CTR-mode AES decription. -struct CTREncryptionLayer : public EncryptionLayer { +class CTREncryptionLayer : public EncryptionLayer { +public: CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset); size_t Read(u8* data, size_t length, size_t offset) const override; - void SetIV(std::vector iv); + void SetIV(const std::vector& iv); private: size_t base_offset; @@ -28,4 +29,4 @@ private: void UpdateIV(size_t offset) const; }; -} // namespace Crypto +} // namespace Core::Crypto diff --git a/src/core/crypto/encryption_layer.cpp b/src/core/crypto/encryption_layer.cpp index 4e243051e2..4204527e33 100644 --- a/src/core/crypto/encryption_layer.cpp +++ b/src/core/crypto/encryption_layer.cpp @@ -4,7 +4,7 @@ #include "core/crypto/encryption_layer.h" -namespace Crypto { +namespace Core::Crypto { EncryptionLayer::EncryptionLayer(FileSys::VirtualFile base_) : base(std::move(base_)) {} @@ -39,4 +39,4 @@ size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) { bool EncryptionLayer::Rename(std::string_view name) { return base->Rename(name); } -} // namespace Crypto +} // namespace Core::Crypto diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h index 2312870d83..84f11bf5e1 100644 --- a/src/core/crypto/encryption_layer.h +++ b/src/core/crypto/encryption_layer.h @@ -3,9 +3,10 @@ // Refer to the license.txt file included. #pragma once + #include "core/file_sys/vfs.h" -namespace Crypto { +namespace Core::Crypto { // Basically non-functional class that implements all of the methods that are irrelevant to an // EncryptionLayer. Reduces duplicate code. @@ -27,4 +28,4 @@ protected: FileSys::VirtualFile base; }; -} // namespace Crypto +} // namespace Core::Crypto diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 05c6a70a24..ea20bc31ba 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -5,238 +5,15 @@ #include #include #include +#include #include "common/assert.h" +#include "common/common_paths.h" +#include "common/file_util.h" #include "common/logging/log.h" #include "core/crypto/key_manager.h" -#include "mbedtls/sha256.h" +#include "core/settings.h" -namespace Crypto { -KeyManager keys = {}; - -std::unordered_map, SHA256Hash> KeyManager::s128_hash_prod = { - {{S128KeyType::MASTER, 0, 0}, - "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32}, - {{S128KeyType::MASTER, 1, 0}, - "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32}, - {{S128KeyType::MASTER, 2, 0}, - "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32}, - {{S128KeyType::MASTER, 3, 0}, - "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32}, - {{S128KeyType::MASTER, 4, 0}, - "75ff1d95d26113550ee6fcc20acb58e97edeb3a2ff52543ed5aec63bdcc3da50"_array32}, - {{S128KeyType::PACKAGE1, 0, 0}, - "4543CD1B7CAD7EE0466A3DE2086A0EF923805DCEA6C741541CDDB14F54F97B40"_array32}, - {{S128KeyType::PACKAGE1, 1, 0}, - "4A11DA019D26470C9B805F1721364830DC0096DD66EAC453B0D14455E5AF5CF8"_array32}, - {{S128KeyType::PACKAGE1, 2, 0}, - "CCA867360B3318246FBF0B8A86473176ED486DFE229772B941A02E84D50A3155"_array32}, - {{S128KeyType::PACKAGE1, 3, 0}, - "E65C383CDF526DFFAA77682868EBFA9535EE60D8075C961BBC1EDE5FBF7E3C5F"_array32}, - {{S128KeyType::PACKAGE1, 4, 0}, - "28ae73d6ae8f7206fca549e27097714e599df1208e57099416ff429b71370162"_array32}, - {{S128KeyType::PACKAGE2, 0, 0}, - "94D6F38B9D0456644E21DFF4707D092B70179B82D1AA2F5B6A76B8F9ED948264"_array32}, - {{S128KeyType::PACKAGE2, 1, 0}, - "7794F24FA879D378FEFDC8776B949B88AD89386410BE9025D463C619F1530509"_array32}, - {{S128KeyType::PACKAGE2, 2, 0}, - "5304BDDE6AC8E462961B5DB6E328B1816D245D36D6574BB78938B74D4418AF35"_array32}, - {{S128KeyType::PACKAGE2, 3, 0}, - "BE1E52C4345A979DDD4924375B91C902052C2E1CF8FBF2FAA42E8F26D5125B60"_array32}, - {{S128KeyType::PACKAGE2, 4, 0}, - "631b45d349ab8f76a050fe59512966fb8dbaf0755ef5b6903048bf036cfa611e"_array32}, - {{S128KeyType::TITLEKEK, 0, 0}, - "C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32}, - {{S128KeyType::TITLEKEK, 1, 0}, - "0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32}, - {{S128KeyType::TITLEKEK, 2, 0}, - "D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32}, - {{S128KeyType::TITLEKEK, 3, 0}, - "47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32}, - {{S128KeyType::TITLEKEK, 4, 0}, - "128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32}, - {{S128KeyType::ETICKET_RSA_KEK, 0, 0}, - "46cccf288286e31c931379de9efa288c95c9a15e40b00a4c563a8be244ece515"_array32}, - {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Application)}, - "592957F44FE5DB5EC6B095F568910E31A226D3B7FE42D64CFB9CE4051E90AEB6"_array32}, - {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Application)}, - "C2252A0FBF9D339ABC3D681351D00452F926E7CA0C6CA85F659078DE3FA647F3"_array32}, - {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Application)}, - "7C7722824B2F7C4938C40F3EA93E16CB69D3285EB133490EF8ECCD2C4B52DF41"_array32}, - {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Application)}, - "AFBB8EBFB2094F1CF71E330826AE06D64414FCA128C464618DF30EED92E62BE6"_array32}, - {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Application)}, - "5dc10eb81918da3f2fa90f69c8542511963656cfb31fb7c779581df8faf1f2f5"_array32}, - {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Ocean)}, - "AA2C65F0E27F730807A13F2ED5B99BE5183165B87C50B6ED48F5CAC2840687EB"_array32}, - {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Ocean)}, - "860185F2313A14F7006A029CB21A52750E7718C1E94FFB98C0AE2207D1A60165"_array32}, - {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Ocean)}, - "7283FB1EFBD42438DADF363FDB776ED355C98737A2AAE75D0E9283CE1C12A2E4"_array32}, - {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Ocean)}, - "9881C2D3AB70B14C8AA12016FC73ADAD93C6AD9FB59A9ECAD312B6F89E2413EC"_array32}, - {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Ocean)}, - "eaa6a8d242b89e174928fa9549a0f66ec1562e2576fac896f438a2b3c1fb6005"_array32}, - {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::System)}, - "194CF6BD14554DA8D457E14CBFE04E55C8FB8CA52E0AFB3D7CB7084AE435B801"_array32}, - {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::System)}, - "CE1DB7BB6E5962384889DB7A396AFD614F82F69DC38A33D2DEAF47F3E4B964B7"_array32}, - {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::System)}, - "42238DE5685DEF4FDE7BE42C0097CEB92447006386D6B5D5AAA2C9AFD2E28422"_array32}, - {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::System)}, - "1F6847F268E9D9C5D1AD4D7E226A63B833BF02071446957A962EF065521879C1"_array32}, - {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::System)}, - "644007f9913c3602399d4d75cc34faeb7f1faad18b23e34187b16fdc45f4980f"_array32}, -}; - -std::unordered_map, SHA256Hash> KeyManager::s256_hash_prod = { - {{S256KeyType::HEADER, 0, 0}, - "8E03DE24818D96CE4F2A09B43AF979E679974F7570713A61EED8B314864A11D5"_array32}, - {{S256KeyType::SD_SAVE, 0, 0}, - "13020ee72d0f8b8f9112dc738b829fdb017102499a7c2259b52aeefc0a273f5c"_array32}, - {{S256KeyType::SD_NCA, 0, 0}, - "8a1c05b4f88bae5b04d77f632e6acfc8893c4a05fd701f53585daafc996b532a"_array32}, -}; - -// TODO(DarkLordZach): Find missing hashes for dev keys. - -std::unordered_map, SHA256Hash> KeyManager::s128_hash_dev = { - {{S128KeyType::MASTER, 0, 0}, - "779dd8b533a2fb670f27b308cb8d0151c4a107568b817429172b7f80aa592c25"_array32}, - {{S128KeyType::MASTER, 1, 0}, - "0175c8bc49771576f75527be719098db4ebaf77707206749415663aa3a9ea9cc"_array32}, - {{S128KeyType::MASTER, 2, 0}, - "4f0b4d724e5a8787268157c7ce0767c26d2e2021832aa7020f306d6e260eea42"_array32}, - {{S128KeyType::MASTER, 3, 0}, - "7b5a29586c1f84f66fbfabb94518fc45408bb8e5445253d063dda7cfef2a818c"_array32}, - {{S128KeyType::MASTER, 4, 0}, - "87a61dbb05a8755de7fe069562aab38ebfb266c9eb835f09fa62dacc89c98341"_array32}, - {{S128KeyType::PACKAGE1, 0, 0}, - "166510bc63ae50391ebe4ee4ff90ca31cd0e2dd0ff6be839a2f573ec146cc23a"_array32}, - {{S128KeyType::PACKAGE1, 1, 0}, - "f74cd01b86743139c920ec54a8116c669eea805a0be1583e13fc5bc8de68645b"_array32}, - {{S128KeyType::PACKAGE1, 2, 0}, - "d0cdecd513bb6aa3d9dc6244c977dc8a5a7ea157d0a8747d79e7581146e1f768"_array32}, - {{S128KeyType::PACKAGE1, 3, 0}, - "aa39394d626b3b79f5b7ccc07378b5996b6d09bf0eb6771b0b40c9077fbfde8c"_array32}, - {{S128KeyType::PACKAGE1, 4, 0}, - "8f4754b8988c0e673fc2bbea0534cdd6075c815c9270754ae980aef3e4f0a508"_array32}, - {{S128KeyType::PACKAGE2, 0, 0}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::PACKAGE2, 1, 0}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::PACKAGE2, 2, 0}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::PACKAGE2, 3, 0}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::PACKAGE2, 4, 0}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::TITLEKEK, 0, 0}, - "C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32}, - {{S128KeyType::TITLEKEK, 1, 0}, - "0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32}, - {{S128KeyType::TITLEKEK, 2, 0}, - "D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32}, - {{S128KeyType::TITLEKEK, 3, 0}, - "47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32}, - {{S128KeyType::TITLEKEK, 4, 0}, - "128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32}, - {{S128KeyType::ETICKET_RSA_KEK, 0, 0}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Application)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Application)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Application)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Application)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Application)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Ocean)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Ocean)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Ocean)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Ocean)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Ocean)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::System)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::System)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::System)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::System)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::System)}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, -}; - -std::unordered_map, SHA256Hash> KeyManager::s256_hash_dev = { - {{S256KeyType::HEADER, 0, 0}, - "ecde86a76e37ac4fd7591d3aa55c00cc77d8595fc27968052ec18a177d939060"_array32}, - {{S256KeyType::SD_SAVE, 0, 0}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, - {{S256KeyType::SD_NCA, 0, 0}, - "0000000000000000000000000000000000000000000000000000000000000000"_array32}, -}; - -std::unordered_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}}, - {"master_key_03", {S128KeyType::MASTER, 3, 0}}, - {"master_key_04", {S128KeyType::MASTER, 4, 0}}, - {"package1_key_00", {S128KeyType::PACKAGE1, 0, 0}}, - {"package1_key_01", {S128KeyType::PACKAGE1, 1, 0}}, - {"package1_key_02", {S128KeyType::PACKAGE1, 2, 0}}, - {"package1_key_03", {S128KeyType::PACKAGE1, 3, 0}}, - {"package1_key_04", {S128KeyType::PACKAGE1, 4, 0}}, - {"package2_key_00", {S128KeyType::PACKAGE2, 0, 0}}, - {"package2_key_01", {S128KeyType::PACKAGE2, 1, 0}}, - {"package2_key_02", {S128KeyType::PACKAGE2, 2, 0}}, - {"package2_key_03", {S128KeyType::PACKAGE2, 3, 0}}, - {"package2_key_04", {S128KeyType::PACKAGE2, 4, 0}}, - {"titlekek_00", {S128KeyType::TITLEKEK, 0, 0}}, - {"titlekek_01", {S128KeyType::TITLEKEK, 1, 0}}, - {"titlekek_02", {S128KeyType::TITLEKEK, 2, 0}}, - {"titlekek_03", {S128KeyType::TITLEKEK, 3, 0}}, - {"titlekek_04", {S128KeyType::TITLEKEK, 4, 0}}, - {"eticket_rsa_kek", {S128KeyType::ETICKET_RSA_KEK, 0, 0}}, - {"key_area_key_application_00", - {S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Application)}}, - {"key_area_key_application_01", - {S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Application)}}, - {"key_area_key_application_02", - {S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Application)}}, - {"key_area_key_application_03", - {S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Application)}}, - {"key_area_key_application_04", - {S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Application)}}, - {"key_area_key_ocean_00", {S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::Ocean)}}, - {"key_area_key_ocean_01", {S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::Ocean)}}, - {"key_area_key_ocean_02", {S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::Ocean)}}, - {"key_area_key_ocean_03", {S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::Ocean)}}, - {"key_area_key_ocean_04", {S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::Ocean)}}, - {"key_area_key_system_00", - {S128KeyType::KEY_AREA, 0, static_cast(KeyAreaKeyType::System)}}, - {"key_area_key_system_01", - {S128KeyType::KEY_AREA, 1, static_cast(KeyAreaKeyType::System)}}, - {"key_area_key_system_02", - {S128KeyType::KEY_AREA, 2, static_cast(KeyAreaKeyType::System)}}, - {"key_area_key_system_03", - {S128KeyType::KEY_AREA, 3, static_cast(KeyAreaKeyType::System)}}, - {"key_area_key_system_04", - {S128KeyType::KEY_AREA, 4, static_cast(KeyAreaKeyType::System)}}, -}; - -std::unordered_map> KeyManager::s256_file_id = { - {"header_key", {S256KeyType::HEADER, 0, 0}}, - {"sd_card_save_key", {S256KeyType::SD_SAVE, 0, 0}}, - {"sd_card_nca_key", {S256KeyType::SD_NCA, 0, 0}}, -}; +namespace Core::Crypto { static u8 ToHexNibble(char c1) { if (c1 >= 65 && c1 <= 70) @@ -271,8 +48,20 @@ std::array operator""_array32(const char* str, size_t len) { return HexStringToArray<32>(str); } -void KeyManager::SetValidationMode(bool dev) { - dev_mode = dev; +KeyManager::KeyManager() { + // Initialize keys + std::string keys_dir = FileUtil::GetHactoolConfigurationPath(); + if (Settings::values.use_dev_keys) { + dev_mode = true; + if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys")) + LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false); + } else { + dev_mode = false; + if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys")) + LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false); + } + if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys")) + LoadFromFile(keys_dir + DIR_SEP + "title.keys", true); } void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { @@ -299,7 +88,7 @@ void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { auto rights_id_raw = HexStringToArray<16>(out[0]); u128 rights_id = *reinterpret_cast*>(&rights_id_raw); Key128 key = HexStringToArray<16>(out[1]); - SetKey(S128KeyType::TITLEKEY, key, rights_id[1], rights_id[0]); + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); } else { std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower); if (s128_file_id.find(out[0]) != s128_file_id.end()) { @@ -315,24 +104,24 @@ void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { } } -bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) { +bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const { return s128_keys.find({id, field1, field2}) != s128_keys.end(); } -bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) { +bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const { return s256_keys.find({id, field1, field2}) != s256_keys.end(); } -Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) { +Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const { if (!HasKey(id, field1, field2)) return {}; - return s128_keys[{id, field1, field2}]; + return s128_keys.at({id, field1, field2}); } -Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) { +Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { if (!HasKey(id, field1, field2)) return {}; - return s256_keys[{id, field1, field2}]; + return s256_keys.at({id, field1, field2}); } void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { @@ -343,68 +132,53 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { s256_keys[{id, field1, field2}] = key; } -bool KeyManager::ValidateKey(S128KeyType key, u64 field1, u64 field2) { - auto& hash = dev_mode ? s128_hash_dev : s128_hash_prod; +std::unordered_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}}, + {"master_key_03", {S128KeyType::Master, 3, 0}}, + {"master_key_04", {S128KeyType::Master, 4, 0}}, + {"package1_key_00", {S128KeyType::Package1, 0, 0}}, + {"package1_key_01", {S128KeyType::Package1, 1, 0}}, + {"package1_key_02", {S128KeyType::Package1, 2, 0}}, + {"package1_key_03", {S128KeyType::Package1, 3, 0}}, + {"package1_key_04", {S128KeyType::Package1, 4, 0}}, + {"package2_key_00", {S128KeyType::Package2, 0, 0}}, + {"package2_key_01", {S128KeyType::Package2, 1, 0}}, + {"package2_key_02", {S128KeyType::Package2, 2, 0}}, + {"package2_key_03", {S128KeyType::Package2, 3, 0}}, + {"package2_key_04", {S128KeyType::Package2, 4, 0}}, + {"titlekek_00", {S128KeyType::Titlekek, 0, 0}}, + {"titlekek_01", {S128KeyType::Titlekek, 1, 0}}, + {"titlekek_02", {S128KeyType::Titlekek, 2, 0}}, + {"titlekek_03", {S128KeyType::Titlekek, 3, 0}}, + {"titlekek_04", {S128KeyType::Titlekek, 4, 0}}, + {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, + {"key_area_key_application_00", + {S128KeyType::KeyArea, 0, static_cast(KeyAreaKeyType::Application)}}, + {"key_area_key_application_01", + {S128KeyType::KeyArea, 1, static_cast(KeyAreaKeyType::Application)}}, + {"key_area_key_application_02", + {S128KeyType::KeyArea, 2, static_cast(KeyAreaKeyType::Application)}}, + {"key_area_key_application_03", + {S128KeyType::KeyArea, 3, static_cast(KeyAreaKeyType::Application)}}, + {"key_area_key_application_04", + {S128KeyType::KeyArea, 4, static_cast(KeyAreaKeyType::Application)}}, + {"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast(KeyAreaKeyType::Ocean)}}, + {"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast(KeyAreaKeyType::Ocean)}}, + {"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast(KeyAreaKeyType::Ocean)}}, + {"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast(KeyAreaKeyType::Ocean)}}, + {"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast(KeyAreaKeyType::Ocean)}}, + {"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast(KeyAreaKeyType::System)}}, + {"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast(KeyAreaKeyType::System)}}, + {"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)}}, +}; - KeyIndex id = {key, field1, field2}; - if (key == S128KeyType::SD_SEED || key == S128KeyType::TITLEKEY || - hash.find(id) == hash.end()) { - LOG_WARNING(Crypto, "Could not validate [{}]", id.DebugInfo()); - return true; - } - - if (!HasKey(key, field1, field2)) { - LOG_CRITICAL(Crypto, - "System has requested validation of [{}], but user has not added it. Add this " - "key to use functionality.", - id.DebugInfo()); - return false; - } - - SHA256Hash key_hash{}; - const auto a_key = GetKey(key, field1, field2); - mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0); - if (key_hash != hash[id]) { - LOG_CRITICAL(Crypto, - "The hash of the provided key for [{}] does not match the one on file. This " - "means you probably have an incorrect key. If you believe this to be in " - "error, contact the yuzu devs.", - id.DebugInfo()); - return false; - } - - return true; -} - -bool KeyManager::ValidateKey(S256KeyType key, u64 field1, u64 field2) { - auto& hash = dev_mode ? s256_hash_dev : s256_hash_prod; - - KeyIndex id = {key, field1, field2}; - if (hash.find(id) == hash.end()) { - LOG_ERROR(Crypto, "Could not validate [{}]", id.DebugInfo()); - return true; - } - - if (!HasKey(key, field1, field2)) { - LOG_ERROR(Crypto, - "System has requested validation of [{}], but user has not added it. Add this " - "key to use functionality.", - id.DebugInfo()); - return false; - } - - SHA256Hash key_hash{}; - const auto a_key = GetKey(key, field1, field2); - mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0); - if (key_hash != hash[id]) { - LOG_CRITICAL(Crypto, - "The hash of the provided key for [{}] does not match the one on file. This " - "means you probably have an incorrect key. If you believe this to be in " - "error, contact the yuzu devs.", - id.DebugInfo()); - return false; - } - - return true; -} -} // namespace Crypto +std::unordered_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}}, +}; +} // namespace Core::Crypto diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index b892a83f2c..e04f1d49f5 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -3,36 +3,37 @@ // Refer to the license.txt file included. #pragma once + #include #include #include #include #include "common/common_types.h" -namespace Crypto { +namespace Core::Crypto { -typedef std::array Key128; -typedef std::array Key256; -typedef std::array SHA256Hash; +using Key128 = std::array; +using Key256 = std::array; +using SHA256Hash = std::array; 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, // - SD_SAVE, // - SD_NCA, // + Header, // + SDSave, // + SDNCA, // }; enum class S128KeyType : u64 { - MASTER, // f1=crypto revision - PACKAGE1, // f1=crypto revision - PACKAGE2, // f1=crypto revision - TITLEKEK, // f1=crypto revision - ETICKET_RSA_KEK, // - KEY_AREA, // f1=crypto revision f2=type {app, ocean, system} - SD_SEED, // - TITLEKEY, // f1=rights id LSB f2=rights id MSB + Master, // f1=crypto revision + Package1, // f1=crypto revision + Package2, // f1=crypto revision + Titlekek, // f1=crypto revision + ETicketRSAKek, // + KeyArea, // f1=crypto revision f2=type {app, ocean, system} + SDSeed, // + Titlekey, // f1=rights id LSB f2=rights id MSB }; enum class KeyAreaKeyType : u8 { @@ -47,7 +48,7 @@ struct KeyIndex { u64 field1; u64 field2; - std::string DebugInfo() { + std::string DebugInfo() const { u8 key_size = 16; if (std::is_same_v) key_size = 32; @@ -60,15 +61,20 @@ struct KeyIndex { template bool operator==(const KeyIndex& lhs, const KeyIndex& rhs) { - return 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); } -} // namespace Crypto +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 Crypto::KeyIndex& k) const { +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) ^ @@ -77,41 +83,32 @@ struct hash> { }; } // namespace std -namespace Crypto { +namespace Core::Crypto { std::array operator"" _array16(const char* str, size_t len); std::array operator"" _array32(const char* str, size_t len); -struct KeyManager { - void SetValidationMode(bool dev); - void LoadFromFile(std::string_view filename, bool is_title_keys); +class KeyManager { +public: + KeyManager(); - bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0); - bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0); + bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const; + bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const; - Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0); - Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0); + Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const; + Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const; void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); - bool ValidateKey(S128KeyType key, u64 field1 = 0, u64 field2 = 0); - bool ValidateKey(S256KeyType key, u64 field1 = 0, u64 field2 = 0); - private: std::unordered_map, Key128> s128_keys; std::unordered_map, Key256> s256_keys; - bool dev_mode = false; + bool dev_mode; + void LoadFromFile(std::string_view filename, bool is_title_keys); - static std::unordered_map, SHA256Hash> s128_hash_prod; - static std::unordered_map, SHA256Hash> s256_hash_prod; - static std::unordered_map, SHA256Hash> s128_hash_dev; - static std::unordered_map, SHA256Hash> s256_hash_dev; static std::unordered_map> s128_file_id; static std::unordered_map> s256_file_id; }; - -extern KeyManager keys; - -} // namespace Crypto +} // namespace Core::Crypto diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 5ff09a362f..3c1dbf46ce 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -30,8 +30,8 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { return; } - const static std::array partition_names = {"update", "normal", "secure", - "logo"}; + static constexpr std::array partition_names = {"update", "normal", "secure", + "logo"}; for (XCIPartition partition : {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { @@ -93,12 +93,9 @@ VirtualDir XCI::GetLogoPartition() const { } std::shared_ptr XCI::GetNCAByType(NCAContentType type) const { - for (const auto& nca : ncas) { - if (nca->GetType() == type) - return nca; - } - - return nullptr; + auto iter = std::find_if(ncas.begin(), ncas.end(), + [type](std::shared_ptr nca) { return nca->GetType() == type; }); + return iter == ncas.end() ? nullptr : *iter; } VirtualFile XCI::GetNCAFileByType(NCAContentType type) const { @@ -133,7 +130,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { return Loader::ResultStatus::ErrorInvalidFormat; } - for (VirtualFile file : partitions[static_cast(part)]->GetFiles()) { + for (const VirtualFile& file : partitions[static_cast(part)]->GetFiles()) { if (file->GetExtension() != "nca") continue; auto nca = std::make_shared(file); diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index add01974e2..952dc70683 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -9,10 +9,9 @@ #include "core/crypto/aes_util.h" #include "core/crypto/ctr_encryption_layer.h" #include "core/file_sys/content_archive.h" +#include "core/file_sys/romfs.h" #include "core/file_sys/vfs_offset.h" #include "core/loader/loader.h" -#include "mbedtls/cipher.h" -#include "romfs.h" namespace FileSys { @@ -77,7 +76,7 @@ bool IsValidNCA(const NCAHeader& header) { return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); } -Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) { +Core::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; @@ -85,16 +84,12 @@ Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) { --master_key_id; std::vector 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 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); + Core::Crypto::AESCipher 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); - Crypto::Key128 out; + Core::Crypto::Key128 out; if (type == NCASectionCryptoType::XTS) std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); else if (type == NCASectionCryptoType::CTR) @@ -102,8 +97,8 @@ Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) { else LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", static_cast(type)); - - u128 out_128 = *reinterpret_cast(&out); + u128 out_128{}; + memcpy(out_128.data(), out.data(), 16); 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]); @@ -121,9 +116,9 @@ VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_o case NCASectionCryptoType::CTR: LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); { - auto out = std::make_shared( + auto out = std::make_shared( std::move(in), GetKeyAreaKey(NCASectionCryptoType::CTR), starting_offset); - std::vector iv(16, 0); + std::vector iv(16); for (u8 i = 0; i < 8; ++i) iv[i] = header.raw.section_ctr[0x8 - i - 1]; out->SetIV(iv); @@ -146,13 +141,10 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { if (!IsValidNCA(header)) { NCAHeader dec_header{}; - if (!Crypto::keys.ValidateKey(Crypto::S256KeyType::HEADER)) { - status = Loader::ResultStatus::ErrorEncrypted; - return; - } - Crypto::AESCipher cipher(Crypto::keys.GetKey(Crypto::S256KeyType::HEADER), - Crypto::Mode::XTS); - cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, Crypto::Op::DECRYPT); + Core::Crypto::AESCipher cipher( + keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); + cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, + Core::Crypto::Op::Decrypt); if (IsValidNCA(dec_header)) { header = dec_header; encrypted = true; @@ -171,14 +163,10 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { 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 cipher(Crypto::keys.GetKey(Crypto::S256KeyType::HEADER), - Crypto::Mode::XTS); + Core::Crypto::AESCipher cipher( + keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, - Crypto::Op::DECRYPT); + Core::Crypto::Op::Decrypt); } else { file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index d9ad3bf7ed..153142b06a 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -9,12 +9,12 @@ #include #include -#include "core/loader/loader.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" #include "core/crypto/key_manager.h" #include "core/file_sys/partition_filesystem.h" +#include "core/loader/loader.h" namespace FileSys { enum class NCAContentType : u8 { @@ -107,7 +107,8 @@ private: bool encrypted; - Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type); + Core::Crypto::KeyManager keys; + Core::Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type); VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset); }; diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 57d0aeb854..dae1c16ef0 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -297,10 +297,9 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block if (f1_vs != f2_vs) return false; - for (size_t j = 0; j < f1_vs; ++j) { - if (f1_v[j] != f2_v[j]) - return false; - } + auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end()); + if (iters.first != f1_v.end() && iters.second != f2_v.end()) + return false; } return true; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 9759e33d1e..b4de5bd169 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -28,14 +28,17 @@ AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) nca_loader(std::make_unique( xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {} +AppLoader_XCI::~AppLoader_XCI() = default; + FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) { FileSys::XCI xci(file); if (xci.GetStatus() == ResultStatus::Success && xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr && AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) == - FileType::NCA) + FileType::NCA) { return FileType::XCI; + } return FileType::Error; } @@ -62,6 +65,4 @@ ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) { return nca_loader->ReadProgramId(out_program_id); } -AppLoader_XCI::~AppLoader_XCI() = default; - } // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index a9cee1ca35..2a09caa5f8 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -13,6 +13,7 @@ namespace Loader { class AppLoader_XCI final : public AppLoader { public: explicit AppLoader_XCI(FileSys::VirtualFile file); + ~AppLoader_XCI(); /** * Returns the type of the file @@ -30,8 +31,6 @@ public: ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadProgramId(u64& out_program_id) override; - ~AppLoader_XCI(); - private: FileSys::ProgramMetadata metadata; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index d3523e6ac2..c274e5e2bd 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include "common/common_paths.h" #include "common/logging/backend.h" #include "common/logging/filter.h" @@ -24,6 +23,7 @@ #include "common/scope_exit.h" #include "common/string_util.h" #include "core/core.h" +#include "core/crypto/key_manager.h" #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" @@ -89,19 +89,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { ui.setupUi(this); statusBar()->hide(); - // Initialize keys - std::string keys_dir = FileUtil::GetHactoolConfigurationPath(); - if (Settings::values.use_dev_keys) { - Crypto::keys.SetValidationMode(true); - if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys")) - Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false); - } else { - if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys")) - Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false); - } - if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys")) - Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "title.keys", true); - default_theme_paths = QIcon::themeSearchPaths(); UpdateUITheme(); diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 955e2ba140..97a8e13f01 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -73,19 +73,6 @@ static void InitializeLogging() { int main(int argc, char** argv) { Config config; - // Initialize keys - std::string keys_dir = FileUtil::GetHactoolConfigurationPath(); - if (Settings::values.use_dev_keys) { - Crypto::keys.SetValidationMode(true); - if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys")) - Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false); - } else { - if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys")) - Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false); - } - if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys")) - Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "title.keys", true); - int option_index = 0; bool use_gdbstub = Settings::values.use_gdbstub; u32 gdb_port = static_cast(Settings::values.gdbstub_port); From cc8234fa89f1a7dcc2709e6d3be03f8458c841d4 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sun, 29 Jul 2018 17:45:12 -0400 Subject: [PATCH 06/13] Use SHGetKnownFolderPath instead of SHGetFolderPathA --- src/common/file_util.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index a26702f54c..ed38c3ced2 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -738,10 +738,11 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { std::string GetHactoolConfigurationPath() { #ifdef _WIN32 - char path[MAX_PATH]; - if (SHGetFolderPathA(nullptr, CSIDL_PROFILE, nullptr, 0, path) != S_OK) + PWSTR pw_local_path = nullptr; + if (SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &pw_local_path) != S_OK) return ""; - std::string local_path = Common::StringFromFixedZeroTerminatedBuffer(path, MAX_PATH); + std::string local_path = Common::UTF16ToUTF8(pw_local_path); + CoTaskMemFree(pw_local_path); return local_path + "\\.switch"; #else return GetHomeDirectory() + "/.switch"; From 150527ec194107f0ba5ea9bdef487782e64090ef Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sun, 29 Jul 2018 18:42:04 -0400 Subject: [PATCH 07/13] Allow key loading from %YUZU_DIR%/keys in addition to ~/.switch --- src/common/common_paths.h | 1 + src/common/file_util.cpp | 1 + src/common/file_util.h | 1 + src/core/crypto/key_manager.cpp | 25 ++++++++++++++++++------- src/core/crypto/key_manager.h | 2 ++ 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/common/common_paths.h b/src/common/common_paths.h index 6799a357ae..df2ce80b13 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -32,6 +32,7 @@ #define SDMC_DIR "sdmc" #define NAND_DIR "nand" #define SYSDATA_DIR "sysdata" +#define KEYS_DIR "keys" #define LOG_DIR "log" // Filenames diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index ed38c3ced2..7aeda737f4 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -706,6 +706,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); + paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); // TODO: Put the logs in a better location for each OS paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); } diff --git a/src/common/file_util.h b/src/common/file_util.h index d530d86c9b..28697d527c 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -23,6 +23,7 @@ namespace FileUtil { enum class UserPath { CacheDir, ConfigDir, + KeysDir, LogDir, NANDDir, RootDir, diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index ea20bc31ba..dea092b5e2 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -12,6 +12,7 @@ #include "common/logging/log.h" #include "core/crypto/key_manager.h" #include "core/settings.h" +#include "key_manager.h" namespace Core::Crypto { @@ -50,18 +51,17 @@ std::array operator""_array32(const char* str, size_t len) { KeyManager::KeyManager() { // Initialize keys - std::string keys_dir = FileUtil::GetHactoolConfigurationPath(); + std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); + std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); if (Settings::values.use_dev_keys) { dev_mode = true; - if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys")) - LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false); + AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); } else { dev_mode = false; - if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys")) - LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false); + AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false); } - if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys")) - LoadFromFile(keys_dir + DIR_SEP + "title.keys", true); + + AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); } void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { @@ -104,6 +104,17 @@ void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { } } +void KeyManager::AttemptLoadKeyFile(std::string_view dir1_, std::string_view dir2_, + std::string_view filename_, bool title) { + std::string dir1(dir1_); + std::string dir2(dir2_); + std::string filename(filename_); + if (FileUtil::Exists(dir1 + DIR_SEP + filename)) + LoadFromFile(dir1 + DIR_SEP + filename, title); + else if (FileUtil::Exists(dir2 + DIR_SEP + filename)) + LoadFromFile(dir2 + DIR_SEP + filename, title); +} + bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const { return s128_keys.find({id, field1, field2}) != s128_keys.end(); } diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index e04f1d49f5..a52ea4cb98 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -107,6 +107,8 @@ private: bool dev_mode; void LoadFromFile(std::string_view filename, bool is_title_keys); + void AttemptLoadKeyFile(std::string_view dir1, std::string_view dir2, std::string_view filename, + bool title); static std::unordered_map> s128_file_id; static std::unordered_map> s256_file_id; From 03149d3e4a7f8038d9c88cbeb19dee25a39e0042 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sun, 29 Jul 2018 19:00:09 -0400 Subject: [PATCH 08/13] Add missing includes and use const where applicable --- src/core/crypto/aes_util.h | 7 +++++-- src/core/crypto/ctr_encryption_layer.cpp | 7 +++---- src/core/crypto/ctr_encryption_layer.h | 1 + src/core/crypto/encryption_layer.h | 3 ++- src/core/crypto/key_manager.cpp | 13 ++++++++----- src/core/crypto/key_manager.h | 7 ++++--- src/core/file_sys/card_image.cpp | 5 +++-- src/core/file_sys/card_image.h | 3 +++ src/core/file_sys/content_archive.cpp | 4 ++-- src/core/file_sys/content_archive.h | 11 ++++++----- src/core/loader/xci.h | 3 +++ 11 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h index fa77d55601..5b0b02738e 100644 --- a/src/core/crypto/aes_util.h +++ b/src/core/crypto/aes_util.h @@ -4,11 +4,16 @@ #pragma once +#include +#include +#include #include "common/assert.h" #include "core/file_sys/vfs.h" namespace Core::Crypto { +struct CipherContext; + enum class Mode { CTR = 11, ECB = 2, @@ -20,8 +25,6 @@ enum class Op { Decrypt, }; -struct CipherContext; - template class AESCipher { static_assert(std::is_same_v>, "Key must be std::array of u8."); diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp index 5dbc257e5e..106db02b39 100644 --- a/src/core/crypto/ctr_encryption_layer.cpp +++ b/src/core/crypto/ctr_encryption_layer.cpp @@ -2,7 +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/ctr_encryption_layer.h" @@ -33,11 +33,10 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { size_t read = 0x10 - sector_offset; if (length + sector_offset < 0x10) { - memcpy(data, block.data() + sector_offset, std::min(length, read)); + std::memcpy(data, block.data() + sector_offset, std::min(length, read)); return read; } - - memcpy(data, block.data() + sector_offset, read); + std::memcpy(data, block.data() + sector_offset, read); return read + Read(data + read, length - read, offset + read); } diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h index 697d7c6a56..11b8683c78 100644 --- a/src/core/crypto/ctr_encryption_layer.h +++ b/src/core/crypto/ctr_encryption_layer.h @@ -4,6 +4,7 @@ #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/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h index 84f11bf5e1..71bca1f23c 100644 --- a/src/core/crypto/encryption_layer.h +++ b/src/core/crypto/encryption_layer.h @@ -10,7 +10,8 @@ namespace Core::Crypto { // Basically non-functional class that implements all of the methods that are irrelevant to an // EncryptionLayer. Reduces duplicate code. -struct EncryptionLayer : public FileSys::VfsFile { +class EncryptionLayer : public FileSys::VfsFile { +public: explicit EncryptionLayer(FileSys::VirtualFile base); size_t Read(u8* data, size_t length, size_t offset) const override = 0; diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index dea092b5e2..33633de7eb 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -2,9 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include +#include #include #include "common/assert.h" #include "common/common_paths.h" @@ -86,17 +88,18 @@ void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { if (is_title_keys) { auto rights_id_raw = HexStringToArray<16>(out[0]); - u128 rights_id = *reinterpret_cast*>(&rights_id_raw); + u128 rights_id{}; + std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); Key128 key = HexStringToArray<16>(out[1]); SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); } 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[out[0]]; + const auto index = s128_file_id.at(out[0]); Key128 key = HexStringToArray<16>(out[1]); SetKey(index.type, key, index.field1, index.field2); } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { - const auto index = s256_file_id[out[0]]; + const auto index = s256_file_id.at(out[0]); Key256 key = HexStringToArray<32>(out[1]); SetKey(index.type, key, index.field1, index.field2); } @@ -143,7 +146,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { s256_keys[{id, field1, field2}] = key; } -std::unordered_map> KeyManager::s128_file_id = { +const std::unordered_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}}, @@ -187,7 +190,7 @@ std::unordered_map> KeyManager::s128_file_id {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast(KeyAreaKeyType::System)}}, }; -std::unordered_map> KeyManager::s256_file_id = { +const std::unordered_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 a52ea4cb98..28a560a3f5 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -50,7 +51,7 @@ struct KeyIndex { std::string DebugInfo() const { u8 key_size = 16; - if (std::is_same_v) + if constexpr (std::is_same_v) key_size = 32; return fmt::format("key_size={:02X}, key={:02X}, field1={:016X}, field2={:016X}", key_size, static_cast(type), field1, field2); @@ -110,7 +111,7 @@ private: void AttemptLoadKeyFile(std::string_view dir1, std::string_view dir2, std::string_view filename, bool title); - static std::unordered_map> s128_file_id; - static std::unordered_map> s256_file_id; + const static std::unordered_map> s128_file_id; + const static std::unordered_map> s256_file_id; }; } // namespace Core::Crypto diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 3c1dbf46ce..c69812455b 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -93,8 +93,9 @@ VirtualDir XCI::GetLogoPartition() const { } std::shared_ptr XCI::GetNCAByType(NCAContentType type) const { - auto iter = std::find_if(ncas.begin(), ncas.end(), - [type](std::shared_ptr nca) { return nca->GetType() == type; }); + const auto iter = + std::find_if(ncas.begin(), ncas.end(), + [type](const std::shared_ptr& nca) { return nca->GetType() == type; }); return iter == ncas.end() ? nullptr : *iter; } diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index b765c8bc10..e089d737cb 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -4,10 +4,13 @@ #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 { diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 952dc70683..9eceaa4c4b 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -76,7 +76,7 @@ bool IsValidNCA(const NCAHeader& header) { return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); } -Core::Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) { +Core::Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) const { u8 master_key_id = header.crypto_type; if (header.crypto_type_2 > master_key_id) master_key_id = header.crypto_type_2; @@ -105,7 +105,7 @@ Core::Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) { return out; } -VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) { +VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const { if (!encrypted) return in; diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 153142b06a..e68ab02358 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -17,6 +17,9 @@ #include "core/loader/loader.h" namespace FileSys { + +union NCASectionHeader; + enum class NCAContentType : u8 { Program = 0, Meta = 1, @@ -61,8 +64,6 @@ struct NCAHeader { }; static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); -union NCASectionHeader; - inline bool IsDirectoryExeFS(const std::shared_ptr& pfs) { // According to switchbrew, an exefs must only contain these two files: return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; @@ -94,6 +95,9 @@ protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; private: + Core::Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type) const; + VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const; + std::vector dirs; std::vector files; @@ -108,9 +112,6 @@ private: bool encrypted; Core::Crypto::KeyManager keys; - Core::Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type); - - VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset); }; } // namespace FileSys diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 2a09caa5f8..0dbcfbdf83 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -4,7 +4,10 @@ #pragma once +#include +#include "common/common_types.h" #include "core/file_sys/card_image.h" +#include "core/loader/loader.h" #include "core/loader/nca.h" namespace Loader { From a9c921a41dec63f76f80df1f0d5dc3be40fa80de Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sun, 29 Jul 2018 20:47:33 -0400 Subject: [PATCH 09/13] Use ErrorEncrypted where applicable and fix no keys crash --- src/core/file_sys/card_image.cpp | 1 + src/core/file_sys/content_archive.cpp | 43 ++++++++++++++++++--------- src/core/file_sys/content_archive.h | 4 +-- src/core/loader/xci.cpp | 4 +++ 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index c69812455b..395eea8ae0 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -45,6 +45,7 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { status = result; return; } + result = AddNCAFromPartition(XCIPartition::Update); if (result != Loader::ResultStatus::Success) { status = result; diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 9eceaa4c4b..cb59c0a2bf 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -4,7 +4,7 @@ #include #include - +#include #include "common/logging/log.h" #include "core/crypto/aes_util.h" #include "core/crypto/ctr_encryption_layer.h" @@ -76,13 +76,16 @@ bool IsValidNCA(const NCAHeader& header) { return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); } -Core::Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) const { +boost::optional NCA::GetKeyAreaKey(NCASectionCryptoType type) 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; + if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) + return boost::none; + std::vector key_area(header.key_area.begin(), header.key_area.end()); Core::Crypto::AESCipher cipher( keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), @@ -116,13 +119,16 @@ VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_o case NCASectionCryptoType::CTR: LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); { + const auto key = GetKeyAreaKey(NCASectionCryptoType::CTR); + if (key == boost::none) + return nullptr; auto out = std::make_shared( - std::move(in), GetKeyAreaKey(NCASectionCryptoType::CTR), starting_offset); + std::move(in), key.value(), starting_offset); std::vector iv(16); for (u8 i = 0; i < 8; ++i) iv[i] = header.raw.section_ctr[0x8 - i - 1]; out->SetIV(iv); - return out; + return std::static_pointer_cast(out); } case NCASectionCryptoType::XTS: // TODO(DarkLordZach): Implement XTSEncryptionLayer and title key encryption. @@ -149,7 +155,10 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { header = dec_header; encrypted = true; } else { - status = Loader::ResultStatus::ErrorInvalidFormat; + if (!keys.HasKey(Core::Crypto::S256KeyType::Header)) + status = Loader::ResultStatus::ErrorEncrypted; + else + status = Loader::ResultStatus::ErrorInvalidFormat; return; } } @@ -179,23 +188,29 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + 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( + const auto dec = Decrypt(section, std::make_shared(file, romfs_size, romfs_offset), - romfs_offset)); - romfs = files.back(); + romfs_offset); + if (dec != nullptr) { + files.emplace_back(); + romfs = files.back(); + } } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { u64 offset = (static_cast(header.section_tables[i].media_offset) * MEDIA_OFFSET_MULTIPLIER) + 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( - Decrypt(section, std::make_shared(file, size, offset), offset)); + const auto dec = + Decrypt(section, std::make_shared(file, size, offset), offset); + if (dec != nullptr) { + auto npfs = std::make_shared(dec); - if (npfs->GetStatus() == Loader::ResultStatus::Success) { - dirs.emplace_back(npfs); - if (IsDirectoryExeFS(dirs.back())) - exefs = dirs.back(); + if (npfs->GetStatus() == Loader::ResultStatus::Success) { + dirs.emplace_back(npfs); + if (IsDirectoryExeFS(dirs.back())) + exefs = dirs.back(); + } } } } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index e68ab02358..6492163b53 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -8,7 +8,7 @@ #include #include #include - +#include #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -95,7 +95,7 @@ protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; private: - Core::Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type) const; + boost::optional GetKeyAreaKey(NCASectionCryptoType type) const; VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const; std::vector dirs; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index b4de5bd169..74940fb831 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -48,6 +48,10 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr& process) { return ResultStatus::ErrorAlreadyLoaded; } + if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) { + return ResultStatus::ErrorEncrypted; + } + auto result = nca_loader->Load(process); if (result != ResultStatus::Success) return result; From 9d59b96ef907e8be2560107485f7616a2234c4a8 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sun, 29 Jul 2018 21:00:50 -0400 Subject: [PATCH 10/13] Use static const instead of const static --- src/core/crypto/key_manager.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 28a560a3f5..c09a6197e6 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -111,7 +111,7 @@ private: void AttemptLoadKeyFile(std::string_view dir1, std::string_view dir2, std::string_view filename, bool title); - const static std::unordered_map> s128_file_id; - const static std::unordered_map> s256_file_id; + static const std::unordered_map> s128_file_id; + static const std::unordered_map> s256_file_id; }; } // namespace Core::Crypto From 187d8e215fb157edaa9f3976bebba9a9a7ed103d Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Mon, 30 Jul 2018 12:46:23 -0400 Subject: [PATCH 11/13] Use more descriptive error codes and messages --- src/core/core.cpp | 12 ++++--- src/core/core.h | 14 ++++---- src/core/crypto/key_manager.cpp | 27 +++++++++++--- src/core/crypto/key_manager.h | 2 ++ src/core/file_sys/content_archive.cpp | 10 ++++-- src/core/loader/loader.h | 3 +- src/core/loader/xci.cpp | 2 +- src/yuzu/main.cpp | 51 +++++++++++++++++++++------ src/yuzu_cmd/yuzu.cpp | 14 +++++--- 9 files changed, 101 insertions(+), 34 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index b7f4b45323..0ef6af3fe6 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -101,8 +101,10 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file static_cast(system_mode.second)); switch (system_mode.second) { - case Loader::ResultStatus::ErrorEncrypted: - return ResultStatus::ErrorLoader_ErrorEncrypted; + case Loader::ResultStatus::ErrorMissingKeys: + return ResultStatus::ErrorLoader_ErrorMissingKeys; + case Loader::ResultStatus::ErrorDecrypting: + return ResultStatus::ErrorLoader_ErrorDecrypting; case Loader::ResultStatus::ErrorInvalidFormat: return ResultStatus::ErrorLoader_ErrorInvalidFormat; case Loader::ResultStatus::ErrorUnsupportedArch: @@ -126,8 +128,10 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file System::Shutdown(); switch (load_result) { - case Loader::ResultStatus::ErrorEncrypted: - return ResultStatus::ErrorLoader_ErrorEncrypted; + case Loader::ResultStatus::ErrorMissingKeys: + return ResultStatus::ErrorLoader_ErrorMissingKeys; + case Loader::ResultStatus::ErrorDecrypting: + return ResultStatus::ErrorLoader_ErrorDecrypting; case Loader::ResultStatus::ErrorInvalidFormat: return ResultStatus::ErrorLoader_ErrorInvalidFormat; case Loader::ResultStatus::ErrorUnsupportedArch: diff --git a/src/core/core.h b/src/core/core.h index c123fe4018..a90bff3df3 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -43,12 +43,14 @@ public: /// Enumeration representing the return values of the System Initialize and Load process. enum class ResultStatus : u32 { - Success, ///< Succeeded - ErrorNotInitialized, ///< Error trying to use core prior to initialization - ErrorGetLoader, ///< Error finding the correct application loader - ErrorSystemMode, ///< Error determining the system mode - ErrorLoader, ///< Error loading the specified application - ErrorLoader_ErrorEncrypted, ///< Error loading the specified application due to encryption + Success, ///< Succeeded + ErrorNotInitialized, ///< Error trying to use core prior to initialization + ErrorGetLoader, ///< Error finding the correct application loader + ErrorSystemMode, ///< Error determining the system mode + ErrorLoader, ///< Error loading the specified application + ErrorLoader_ErrorMissingKeys, ///< Error because the key/keys needed to run could not be + ///< found. + ErrorLoader_ErrorDecrypting, ///< Error loading the specified application due to encryption ErrorLoader_ErrorInvalidFormat, ///< Error loading the specified application due to an /// invalid format ErrorSystemFiles, ///< Error in finding system files diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 33633de7eb..678ac57528 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -53,8 +53,8 @@ std::array operator""_array32(const char* str, size_t len) { KeyManager::KeyManager() { // Initialize keys - std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); - std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); + const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); + const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); if (Settings::values.use_dev_keys) { dev_mode = true; AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); @@ -109,9 +109,9 @@ void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { void KeyManager::AttemptLoadKeyFile(std::string_view dir1_, std::string_view dir2_, std::string_view filename_, bool title) { - std::string dir1(dir1_); - std::string dir2(dir2_); - std::string filename(filename_); + const std::string dir1(dir1_); + const std::string dir2(dir2_); + const std::string filename(filename_); if (FileUtil::Exists(dir1 + DIR_SEP + filename)) LoadFromFile(dir1 + DIR_SEP + filename, title); else if (FileUtil::Exists(dir2 + DIR_SEP + filename)) @@ -146,6 +146,23 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { s256_keys[{id, field1, field2}] = key; } +bool KeyManager::KeyFileExists(bool title) { + const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); + const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); + if (title) { + return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || + FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys"); + } + + if (Settings::values.use_dev_keys) { + return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || + FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys"); + } + + return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || + FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); +} + const std::unordered_map> KeyManager::s128_file_id = { {"master_key_00", {S128KeyType::Master, 0, 0}}, {"master_key_01", {S128KeyType::Master, 1, 0}}, diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index c09a6197e6..03152a12c9 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -102,6 +102,8 @@ public: void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); + static bool KeyFileExists(bool title); + private: std::unordered_map, Key128> s128_keys; std::unordered_map, Key256> s256_keys; diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index cb59c0a2bf..2deb727cca 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -156,9 +156,9 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { encrypted = true; } else { if (!keys.HasKey(Core::Crypto::S256KeyType::Header)) - status = Loader::ResultStatus::ErrorEncrypted; + status = Loader::ResultStatus::ErrorMissingKeys; else - status = Loader::ResultStatus::ErrorInvalidFormat; + status = Loader::ResultStatus::ErrorDecrypting; return; } } @@ -194,6 +194,9 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { if (dec != nullptr) { files.emplace_back(); romfs = files.back(); + } else { + status = Loader::ResultStatus::ErrorMissingKeys; + return; } } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { u64 offset = (static_cast(header.section_tables[i].media_offset) * @@ -211,6 +214,9 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { if (IsDirectoryExeFS(dirs.back())) exefs = dirs.back(); } + } else { + status = Loader::ResultStatus::ErrorMissingKeys; + return; } } } diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index dc8d283114..cd8b41bb69 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -73,7 +73,8 @@ enum class ResultStatus { ErrorNotUsed, ErrorAlreadyLoaded, ErrorMemoryAllocationFailed, - ErrorEncrypted, + ErrorMissingKeys, + ErrorDecrypting, ErrorUnsupportedArch, }; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 74940fb831..d757862f0f 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -49,7 +49,7 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr& process) { } if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) { - return ResultStatus::ErrorEncrypted; + return ResultStatus::ErrorDecrypting; } auto result = nca_loader->Load(process); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index c274e5e2bd..5bdf41ea4f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -425,18 +425,49 @@ bool GMainWindow::LoadROM(const QString& filename) { tr("Could not determine the system mode.")); break; - case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: { + case Core::System::ResultStatus::ErrorLoader_ErrorMissingKeys: { + const auto reg_found = Core::Crypto::KeyManager::KeyFileExists(false); + const auto title_found = Core::Crypto::KeyManager::KeyFileExists(true); + + std::string file_text; + + if (!reg_found && !title_found) { + file_text = "A proper key file (prod.keys, dev.keys, or title.keys) could not be " + "found. You will need to dump your keys from your switch to continue."; + } else if (reg_found && title_found) { + file_text = + "Both key files were found in your config directory, but the correct key could" + "not be found. You may be missing a titlekey or general key, depending on " + "the game."; + } else if (reg_found) { + file_text = + "The regular keys file (prod.keys/dev.keys) was found in your config, but the " + "titlekeys file (title.keys) was not. You are either missing the correct " + "titlekey or missing a general key required to decrypt the game."; + } else { + file_text = "The title keys file (title.keys) was found in your config, but " + "the regular keys file (prod.keys/dev.keys) was not. Unfortunately, " + "having the titlekey is not enough, you need additional general keys " + "to properly decrypt the game. You should double-check to make sure " + "your keys are correct."; + } + QMessageBox::critical( this, tr("Error while loading ROM!"), - tr("The game that you are trying to load must be decrypted before being used with " - "yuzu. A real Switch is required.

" - "For more information on dumping and decrypting games, please see the following " - "wiki pages: ")); + tr(("The game you are trying to load is encrypted and the required keys to load " + "the game could not be found in your configuration. " + + file_text + " Please refer to How to Dump Keys for help.") + .c_str())); + break; + } + case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting: { + QMessageBox::critical( + this, tr("Error while loading ROM!"), + tr("There was a general error while decrypting the game. This means that the keys " + "necessary were found, but were either incorrect, the game itself was not a " + "valid game or the game uses an unhandled cryptographic scheme. Please refer to " + "How to Dump Keys to double check that you have the correct " + "keys.")); break; } case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 97a8e13f01..5970cdb4ed 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -173,11 +173,15 @@ int main(int argc, char** argv) { case Core::System::ResultStatus::ErrorLoader: LOG_CRITICAL(Frontend, "Failed to load ROM!"); return -1; - case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: - LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " - "being used with yuzu. \n\n For more information on dumping and " - "decrypting games, please refer to: " - "https://yuzu-emu.org/wiki/dumping-game-cartridges/"); + case Core::System::ResultStatus::ErrorLoader_ErrorMissingKeys: + LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and the keys required " + "could not be found. Please refer to for help"); + return -1; + case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting: + LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and there was a " + "general error while decrypting. This could mean that the keys are " + "incorrect, game is invalid or game uses an unsupported method of " + "crypto. Please refer to to double-check your keys"); return -1; case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); From 0497bb5528f62f9e3db887988f0f93b4a1653a42 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Mon, 30 Jul 2018 22:04:51 -0400 Subject: [PATCH 12/13] Fix merge conflicts with opus and update docs --- .gitmodules | 10 +++++----- src/core/CMakeLists.txt | 2 +- src/core/loader/xci.cpp | 2 ++ src/yuzu/main.cpp | 6 +++--- src/yuzu_cmd/yuzu.cpp | 4 ++-- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.gitmodules b/.gitmodules index 247aac535d..4f4e8690bf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,9 +25,9 @@ [submodule "unicorn"] path = externals/unicorn url = https://github.com/yuzu-emu/unicorn -[submodule "externals/mbedtls"] - path = externals/mbedtls - url = https://github.com/DarkLordZach/mbedtls +[submodule "mbedtls"] + path = externals/mbedtls + url = https://github.com/DarkLordZach/mbedtls [submodule "opus"] - path = externals/opus - url = https://github.com/ogniK5377/opus.git + path = externals/opus + url = https://github.com/ogniK5377/opus.git diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 528d96a58b..22fad97a37 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -348,7 +348,7 @@ add_library(core STATIC create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) -target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static opus unicorn) +target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn) if (ARCHITECTURE_x86_64) target_sources(core PRIVATE diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index d757862f0f..eb4dee2c25 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -49,6 +49,8 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr& process) { } if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) { + if (!Core::Crypto::KeyManager::KeyFileExists(false)) + return ResultStatus::ErrorMissingKeys; return ResultStatus::ErrorDecrypting; } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 5bdf41ea4f..34d4f0754c 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -456,7 +456,7 @@ bool GMainWindow::LoadROM(const QString& filename) { this, tr("Error while loading ROM!"), tr(("The game you are trying to load is encrypted and the required keys to load " "the game could not be found in your configuration. " + - file_text + " Please refer to How to Dump Keys for help.") + file_text + " Please refer to the yuzu wiki for help.") .c_str())); break; } @@ -465,8 +465,8 @@ bool GMainWindow::LoadROM(const QString& filename) { this, tr("Error while loading ROM!"), tr("There was a general error while decrypting the game. This means that the keys " "necessary were found, but were either incorrect, the game itself was not a " - "valid game or the game uses an unhandled cryptographic scheme. Please refer to " - "How to Dump Keys to double check that you have the correct " + "valid game or the game uses an unhandled cryptographic scheme. Please double " + "check that you have the correct " "keys.")); break; } diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 5970cdb4ed..c3b0d9f1f6 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -175,13 +175,13 @@ int main(int argc, char** argv) { return -1; case Core::System::ResultStatus::ErrorLoader_ErrorMissingKeys: LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and the keys required " - "could not be found. Please refer to for help"); + "could not be found. Please refer to the yuzu wiki for help"); return -1; case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting: LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and there was a " "general error while decrypting. This could mean that the keys are " "incorrect, game is invalid or game uses an unsupported method of " - "crypto. Please refer to to double-check your keys"); + "crypto. Please double-check your keys"); return -1; case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); From 13cdf1f15952cdce306e8c241b7d156496f72fe5 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Tue, 31 Jul 2018 12:27:14 -0400 Subject: [PATCH 13/13] Add missing parameter to files.push_back() --- src/core/file_sys/content_archive.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 2deb727cca..79e70f6ef1 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -188,11 +188,11 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; - const auto dec = + auto dec = Decrypt(section, std::make_shared(file, romfs_size, romfs_offset), romfs_offset); if (dec != nullptr) { - files.emplace_back(); + files.push_back(std::move(dec)); romfs = files.back(); } else { status = Loader::ResultStatus::ErrorMissingKeys; @@ -204,13 +204,13 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { section.pfs0.pfs0_header_offset; u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - header.section_tables[i].media_offset); - const auto dec = + auto dec = Decrypt(section, std::make_shared(file, size, offset), offset); if (dec != nullptr) { - auto npfs = std::make_shared(dec); + auto npfs = std::make_shared(std::move(dec)); if (npfs->GetStatus() == Loader::ResultStatus::Success) { - dirs.emplace_back(npfs); + dirs.push_back(std::move(npfs)); if (IsDirectoryExeFS(dirs.back())) exefs = dirs.back(); }