diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 8d1f9a9898..1503bde01c 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -767,11 +767,10 @@ ReturnCode ES::SetUpStreamKey(const u32 uid, const u8* ticket_view, const IOS::E return ret; const u8 index = ticket_bytes[offsetof(IOS::ES::Ticket, common_key_index)]; - if (index > 1) + if (index >= IOSC::COMMON_KEY_HANDLES.size()) return ES_INVALID_TICKET; - auto common_key_handle = index == 0 ? IOSC::HANDLE_COMMON_KEY : IOSC::HANDLE_NEW_COMMON_KEY; - return m_ios.GetIOSC().ImportSecretKey(*handle, common_key_handle, iv.data(), + return m_ios.GetIOSC().ImportSecretKey(*handle, IOSC::COMMON_KEY_HANDLES[index], iv.data(), &ticket_bytes[offsetof(IOS::ES::Ticket, title_key)], PID_ES); } diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index d8a5af7a10..f264d5e494 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -452,14 +452,14 @@ std::array TicketReader::GetTitleKey(const HLE::IOSC& iosc) const u8 iv[16] = {}; std::copy_n(&m_bytes[offsetof(Ticket, title_id)], sizeof(Ticket::title_id), iv); - const u8 index = m_bytes.at(offsetof(Ticket, common_key_index)); - auto common_key_handle = - index != 1 ? HLE::IOSC::HANDLE_COMMON_KEY : HLE::IOSC::HANDLE_NEW_COMMON_KEY; - if (index != 0 && index != 1) + u8 index = m_bytes.at(offsetof(Ticket, common_key_index)); + if (index >= HLE::IOSC::COMMON_KEY_HANDLES.size()) { - WARN_LOG(IOS_ES, "Bad common key index for title %016" PRIx64 ": %u -- using common key 0", - GetTitleId(), index); + PanicAlert("Bad common key index for title %016" PRIx64 ": %u -- using common key 0", + GetTitleId(), index); + index = 0; } + auto common_key_handle = HLE::IOSC::COMMON_KEY_HANDLES[index]; std::array key; iosc.Decrypt(common_key_handle, iv, &m_bytes[offsetof(Ticket, title_key)], 16, key.data(), @@ -533,11 +533,9 @@ HLE::ReturnCode TicketReader::Unpersonalise(HLE::IOSC& iosc) return ret; } -void TicketReader::FixCommonKeyIndex() +void TicketReader::OverwriteCommonKeyIndex(u8 index) { - u8& index = m_bytes[offsetof(Ticket, common_key_index)]; - // Assume the ticket is using the normal common key if it's an invalid value. - index = index <= 1 ? index : 0; + m_bytes[offsetof(Ticket, common_key_index)] = index; } struct SharedContentMap::Entry diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index c9ad6a142e..82b689df32 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -257,9 +257,8 @@ public: // and has a title key that must be decrypted first. HLE::ReturnCode Unpersonalise(HLE::IOSC& iosc); - // Reset the common key field back to 0 if it's an incorrect value. // Intended for use before importing fakesigned tickets, which tend to have a high bogus index. - void FixCommonKeyIndex(); + void OverwriteCommonKeyIndex(u8 index); }; class SharedContentMap final diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index d415e44f2a..612f04d9ab 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -198,12 +198,11 @@ static ReturnCode InitTitleImportKey(const std::vector& ticket_bytes, IOSC& std::array iv{}; std::copy_n(&ticket_bytes[offsetof(IOS::ES::Ticket, title_id)], sizeof(u64), iv.begin()); const u8 index = ticket_bytes[offsetof(IOS::ES::Ticket, common_key_index)]; - if (index > 1) + if (index >= IOSC::COMMON_KEY_HANDLES.size()) return ES_INVALID_TICKET; - return iosc.ImportSecretKey( - *handle, index == 0 ? IOSC::HANDLE_COMMON_KEY : IOSC::HANDLE_NEW_COMMON_KEY, iv.data(), - &ticket_bytes[offsetof(IOS::ES::Ticket, title_key)], PID_ES); + return iosc.ImportSecretKey(*handle, IOSC::COMMON_KEY_HANDLES[index], iv.data(), + &ticket_bytes[offsetof(IOS::ES::Ticket, title_key)], PID_ES); } ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_bytes, diff --git a/Source/Core/Core/IOS/IOSC.h b/Source/Core/Core/IOS/IOSC.h index 8155a5f0a7..e1fa3a76ba 100644 --- a/Source/Core/Core/IOS/IOSC.h +++ b/Source/Core/Core/IOS/IOSC.h @@ -165,6 +165,9 @@ public: HANDLE_ROOT_KEY = 0xfffffff, }; + static constexpr std::array COMMON_KEY_HANDLES = {HANDLE_COMMON_KEY, + HANDLE_NEW_COMMON_KEY}; + enum ObjectType : u8 { TYPE_SECRET_KEY = 0, diff --git a/Source/Core/Core/WiiUtils.cpp b/Source/Core/Core/WiiUtils.cpp index c8e8bf9b20..007d6ee202 100644 --- a/Source/Core/Core/WiiUtils.cpp +++ b/Source/Core/Core/WiiUtils.cpp @@ -61,9 +61,8 @@ static bool ImportWAD(IOS::HLE::Kernel& ios, const DiscIO::VolumeWAD& wad) IOS::HLE::ReturnCode ret; const bool checks_enabled = SConfig::GetInstance().m_enable_signature_checks; - IOS::ES::TicketReader ticket = wad.GetTicket(); // Ensure the common key index is correct, as it's checked by IOS. - ticket.FixCommonKeyIndex(); + IOS::ES::TicketReader ticket = wad.GetTicketWithFixedCommonKey(); while ((ret = es->ImportTicket(ticket.GetBytes(), wad.GetCertificateChain(), IOS::HLE::Device::ES::TicketImportType::Unpersonalised)) < 0 || diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h index ede7a2fc23..a26db94370 100644 --- a/Source/Core/DiscIO/Volume.h +++ b/Source/Core/DiscIO/Volume.h @@ -76,6 +76,12 @@ public: } virtual std::vector GetContent(u16 index) const { return {}; } virtual std::vector GetContentOffsets() const { return {}; } + virtual bool CheckContentIntegrity(const IOS::ES::Content& content, u64 content_offset, + const IOS::ES::TicketReader& ticket) const + { + return false; + } + virtual IOS::ES::TicketReader GetTicketWithFixedCommonKey() const { return {}; } // Returns a non-owning pointer. Returns nullptr if the file system couldn't be read. virtual const FileSystem* GetFileSystem(const Partition& partition) const = 0; virtual u64 PartitionOffsetToRawOffset(u64 offset, const Partition& partition) const diff --git a/Source/Core/DiscIO/VolumeVerifier.cpp b/Source/Core/DiscIO/VolumeVerifier.cpp index b538b7fa77..3850adad22 100644 --- a/Source/Core/DiscIO/VolumeVerifier.cpp +++ b/Source/Core/DiscIO/VolumeVerifier.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -608,29 +607,33 @@ void VolumeVerifier::CheckMisc() } } - const IOS::ES::TicketReader& ticket = m_volume.GetTicket(m_volume.GetGamePartition()); - if (ticket.IsValid()) + m_ticket = m_volume.GetTicket(m_volume.GetGamePartition()); + if (m_ticket.IsValid()) { - const u8 common_key = ticket.GetCommonKeyIndex(); + const u8 specified_common_key_index = m_ticket.GetCommonKeyIndex(); - if (common_key > 1) + // Wii discs only use common key 0 (regular) and common key 1 (Korean), not common key 2 (vWii). + if (m_volume.GetVolumeType() == Platform::WiiDisc && specified_common_key_index > 1) { - // Many fakesigned WADs have the common key index set to a (random?) bogus value. - // For WADs, Dolphin will detect this and use common key 0 instead, making this low severity. - const Severity severity = - m_volume.GetVolumeType() == Platform::WiiWAD ? Severity::Low : Severity::High; - // i18n: This is "common" as in "shared", not the opposite of "uncommon" - AddProblem(severity, Common::GetStringT("This title is set to use an invalid common key.")); - } - - if (common_key == 1 && region != Region::NTSC_K) - { - // Apparently a certain pirate WAD of Chronos Twins DX unluckily got an index of 1, - // which Dolphin does not change to 0 because 1 is valid on Korean Wiis. - // https://forums.dolphin-emu.org/Thread-wiiware-chronos-twins-dx AddProblem(Severity::High, // i18n: This is "common" as in "shared", not the opposite of "uncommon" - Common::GetStringT("This non-Korean title is set to use the Korean common key.")); + Common::GetStringT("This title is set to use an invalid common key.")); + } + + if (m_volume.GetVolumeType() == Platform::WiiWAD) + { + m_ticket = m_volume.GetTicketWithFixedCommonKey(); + const u8 fixed_common_key_index = m_ticket.GetCommonKeyIndex(); + if (specified_common_key_index != fixed_common_key_index) + { + // Many fakesigned WADs have the common key index set to a (random?) bogus value. + // For WADs, Dolphin will detect this and use the correct key, making this low severity. + std::string text = StringFromFormat( + // i18n: This is "common" as in "shared", not the opposite of "uncommon" + Common::GetStringT("The specified common key index is %u but should be %u.").c_str(), + specified_common_key_index, fixed_common_key_index); + AddProblem(Severity::Low, std::move(text)); + } } } @@ -741,7 +744,7 @@ void VolumeVerifier::Process() if (content_read) { - if (!CheckContentIntegrity(content)) + if (!m_volume.CheckContentIntegrity(content, m_content_offsets[m_content_index], m_ticket)) { AddProblem( Severity::High, @@ -772,27 +775,6 @@ void VolumeVerifier::Process() } } -bool VolumeVerifier::CheckContentIntegrity(const IOS::ES::Content& content) -{ - std::vector encrypted_data = m_volume.GetContent(content.index); - - mbedtls_aes_context context; - const std::array key = m_volume.GetTicket(PARTITION_NONE).GetTitleKey(); - mbedtls_aes_setkey_dec(&context, key.data(), 128); - - std::array iv{}; - iv[0] = static_cast(content.index >> 8); - iv[1] = static_cast(content.index & 0xFF); - - std::vector decrypted_data(Common::AlignUp(content.size, 0x40)); - mbedtls_aes_crypt_cbc(&context, MBEDTLS_AES_DECRYPT, decrypted_data.size(), iv.data(), - encrypted_data.data(), decrypted_data.data()); - - std::array sha1; - mbedtls_sha1_ret(decrypted_data.data(), content.size, sha1.data()); - return sha1 == content.sha1; -} - u64 VolumeVerifier::GetBytesProcessed() const { return m_progress; diff --git a/Source/Core/DiscIO/VolumeVerifier.h b/Source/Core/DiscIO/VolumeVerifier.h index 31a4e591d9..9bcf2fa8c2 100644 --- a/Source/Core/DiscIO/VolumeVerifier.h +++ b/Source/Core/DiscIO/VolumeVerifier.h @@ -13,6 +13,7 @@ #include #include "Common/CommonTypes.h" +#include "Core/IOS/ES/Formats.h" #include "DiscIO/DiscScrubber.h" #include "DiscIO/Volume.h" @@ -29,12 +30,6 @@ // // GetResult() can be called before the processing is finished, but the result will be incomplete. -namespace IOS::ES -{ -struct Content; -class SignedBlobReader; -} // namespace IOS::ES - namespace DiscIO { class FileInfo; @@ -103,7 +98,6 @@ private: u64 GetBiggestUsedOffset(const FileInfo& file_info) const; void CheckMisc(); void SetUpHashing(); - bool CheckContentIntegrity(const IOS::ES::Content& content); void AddProblem(Severity severity, std::string text); @@ -120,6 +114,7 @@ private: mbedtls_sha1_context m_sha1_context; DiscScrubber m_scrubber; + IOS::ES::TicketReader m_ticket; std::vector m_content_offsets; u16 m_content_index = 0; std::vector m_blocks; diff --git a/Source/Core/DiscIO/VolumeWad.cpp b/Source/Core/DiscIO/VolumeWad.cpp index e0dda7ebc4..cc83f59081 100644 --- a/Source/Core/DiscIO/VolumeWad.cpp +++ b/Source/Core/DiscIO/VolumeWad.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include #include #include #include @@ -12,12 +13,16 @@ #include #include +#include +#include + #include "Common/Align.h" #include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/StringUtil.h" +#include "Core/IOS/IOSC.h" #include "DiscIO/Blob.h" #include "DiscIO/Enums.h" #include "DiscIO/Volume.h" @@ -147,6 +152,95 @@ std::vector VolumeWAD::GetContentOffsets() const return content_offsets; } +bool VolumeWAD::CheckContentIntegrity(const IOS::ES::Content& content, + const std::vector& encrypted_data, + const IOS::ES::TicketReader& ticket) const +{ + mbedtls_aes_context context; + const std::array key = ticket.GetTitleKey(); + mbedtls_aes_setkey_dec(&context, key.data(), 128); + + std::array iv{}; + iv[0] = static_cast(content.index >> 8); + iv[1] = static_cast(content.index & 0xFF); + + std::vector decrypted_data(encrypted_data.size()); + mbedtls_aes_crypt_cbc(&context, MBEDTLS_AES_DECRYPT, decrypted_data.size(), iv.data(), + encrypted_data.data(), decrypted_data.data()); + + std::array sha1; + mbedtls_sha1_ret(decrypted_data.data(), content.size, sha1.data()); + return sha1 == content.sha1; +} + +bool VolumeWAD::CheckContentIntegrity(const IOS::ES::Content& content, u64 content_offset, + const IOS::ES::TicketReader& ticket) const +{ + std::vector encrypted_data(Common::AlignUp(content.size, 0x40)); + if (!m_reader->Read(content_offset, encrypted_data.size(), encrypted_data.data())) + return false; + return CheckContentIntegrity(content, encrypted_data, ticket); +} + +IOS::ES::TicketReader VolumeWAD::GetTicketWithFixedCommonKey() const +{ + if (!m_ticket.IsValid() || !m_tmd.IsValid()) + return m_ticket; + + const std::vector sig = m_ticket.GetSignatureData(); + if (!std::all_of(sig.cbegin(), sig.cend(), [](u8 a) { return a == 0; })) + { + // This does not look like a typical "invalid common key index" ticket, so let's assume + // the index is correct. This saves some time when reading properly signed titles. + return m_ticket; + } + + const std::vector contents = m_tmd.GetContents(); + if (contents.empty()) + return m_ticket; + + // Find the smallest content so that we spend as little time as possible in CheckContentIntegrity + IOS::ES::Content smallest_content = contents[0]; + u64 offset_of_smallest_content = m_data_offset; + + u64 offset = m_data_offset; + for (const IOS::ES::Content& content : contents) + { + if (content.size < smallest_content.size) + { + smallest_content = content; + offset_of_smallest_content = offset; + } + offset += Common::AlignUp(content.size, 0x40); + } + + std::vector content_data(Common::AlignUp(smallest_content.size, 0x40)); + if (!m_reader->Read(offset_of_smallest_content, content_data.size(), content_data.data())) + return m_ticket; + + const u8 specified_index = m_ticket.GetCommonKeyIndex(); + if (specified_index < IOS::HLE::IOSC::COMMON_KEY_HANDLES.size() && + CheckContentIntegrity(smallest_content, content_data, m_ticket)) + { + return m_ticket; // The common key index is already correct + } + + // Try every common key index except the one we already tried + IOS::ES::TicketReader new_ticket = m_ticket; + for (u8 i = 0; i < IOS::HLE::IOSC::COMMON_KEY_HANDLES.size(); ++i) + { + if (i != specified_index) + { + new_ticket.OverwriteCommonKeyIndex(i); + if (CheckContentIntegrity(smallest_content, content_data, new_ticket)) + return new_ticket; // We've found the common key index that should be used + } + } + + ERROR_LOG(DISCIO, "Couldn't find valid common key for WAD file (%u specified)", specified_index); + return m_ticket; +} + std::string VolumeWAD::GetGameID(const Partition& partition) const { return m_tmd.GetGameID(); diff --git a/Source/Core/DiscIO/VolumeWad.h b/Source/Core/DiscIO/VolumeWad.h index eb85167a96..02bac94e62 100644 --- a/Source/Core/DiscIO/VolumeWad.h +++ b/Source/Core/DiscIO/VolumeWad.h @@ -39,6 +39,9 @@ public: GetCertificateChain(const Partition& partition = PARTITION_NONE) const override; std::vector GetContent(u16 index) const override; std::vector GetContentOffsets() const override; + bool CheckContentIntegrity(const IOS::ES::Content& content, u64 content_offset, + const IOS::ES::TicketReader& ticket) const override; + IOS::ES::TicketReader GetTicketWithFixedCommonKey() const override; std::string GetGameID(const Partition& partition = PARTITION_NONE) const override; std::string GetGameTDBID(const Partition& partition = PARTITION_NONE) const override; std::string GetMakerID(const Partition& partition = PARTITION_NONE) const override; @@ -63,6 +66,9 @@ public: u64 GetRawSize() const override; private: + bool CheckContentIntegrity(const IOS::ES::Content& content, const std::vector& encrypted_data, + const IOS::ES::TicketReader& ticket) const; + std::unique_ptr m_reader; IOS::ES::TicketReader m_ticket; IOS::ES::TMDReader m_tmd;