From 34c9bf391e03ead0fb073495c66d6572dd791d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 10 Jun 2017 18:32:15 +0200 Subject: [PATCH 01/12] IOS: Correct ES return code names about signatures -1027 is used when ES cannot find the issuer of a certificate. -1012 is used when the signature type is invalid. --- Source/Core/Core/IOS/Device.h | 53 ++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/Source/Core/Core/IOS/Device.h b/Source/Core/Core/IOS/Device.h index 13aa85a316..3bf9daf199 100644 --- a/Source/Core/Core/IOS/Device.h +++ b/Source/Core/Core/IOS/Device.h @@ -19,38 +19,39 @@ namespace HLE { enum ReturnCode : s32 { - IPC_SUCCESS = 0, // Success - IPC_EACCES = -1, // Permission denied - IPC_EEXIST = -2, // File exists - IPC_EINVAL = -4, // Invalid argument or fd - IPC_EMAX = -5, // Too many file descriptors open - IPC_ENOENT = -6, // File not found - IPC_EQUEUEFULL = -8, // Queue full - IPC_EIO = -12, // ECC error - IPC_ENOMEM = -22, // Alloc failed during request - FS_EINVAL = -101, // Invalid path - FS_EACCESS = -102, // Permission denied - FS_ECORRUPT = -103, // Corrupted NAND - FS_EEXIST = -105, // File exists - FS_ENOENT = -106, // No such file or directory - FS_ENFILE = -107, // Too many fds open - FS_EFBIG = -108, // Max block count reached? - FS_EFDEXHAUSTED = -109, // Too many fds open - FS_ENAMELEN = -110, // Pathname is too long - FS_EFDOPEN = -111, // FD is already open - FS_EIO = -114, // ECC error - FS_ENOTEMPTY = -115, // Directory not empty - FS_EDIRDEPTH = -116, // Max directory depth exceeded - FS_EBUSY = -118, // Resource busy - ES_SHORT_READ = -1009, // Short read - ES_EIO = -1010, // Write failure + IPC_SUCCESS = 0, // Success + IPC_EACCES = -1, // Permission denied + IPC_EEXIST = -2, // File exists + IPC_EINVAL = -4, // Invalid argument or fd + IPC_EMAX = -5, // Too many file descriptors open + IPC_ENOENT = -6, // File not found + IPC_EQUEUEFULL = -8, // Queue full + IPC_EIO = -12, // ECC error + IPC_ENOMEM = -22, // Alloc failed during request + FS_EINVAL = -101, // Invalid path + FS_EACCESS = -102, // Permission denied + FS_ECORRUPT = -103, // Corrupted NAND + FS_EEXIST = -105, // File exists + FS_ENOENT = -106, // No such file or directory + FS_ENFILE = -107, // Too many fds open + FS_EFBIG = -108, // Max block count reached? + FS_EFDEXHAUSTED = -109, // Too many fds open + FS_ENAMELEN = -110, // Pathname is too long + FS_EFDOPEN = -111, // FD is already open + FS_EIO = -114, // ECC error + FS_ENOTEMPTY = -115, // Directory not empty + FS_EDIRDEPTH = -116, // Max directory depth exceeded + FS_EBUSY = -118, // Resource busy + ES_SHORT_READ = -1009, // Short read + ES_EIO = -1010, // Write failure + ES_INVALID_SIGNATURE_TYPE = -1012, ES_FD_EXHAUSTED = -1016, // Max of 3 ES handles exceeded ES_EINVAL = -1017, // Invalid argument ES_DEVICE_ID_MISMATCH = -1018, ES_HASH_MISMATCH = -1022, // Decrypted content hash doesn't match with the hash from the TMD ES_ENOMEM = -1024, // Alloc failed during request ES_EACCES = -1026, // Incorrect access rights (according to TMD) - ES_INVALID_TMD_SIGNATURE_TYPE = -1027, + ES_UNKNOWN_ISSUER = -1027, ES_NO_TICKET = -1028, ES_INVALID_TICKET = -1029, IOSC_EACCES = -2000, From 88348e29033172a33e14a4b733f939a3184d3191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 11 Jun 2017 15:00:33 +0200 Subject: [PATCH 02/12] IOS/ES: Add VerifyContainer Will be used from several functions to verify the signatures for different containers (TMDs, tickets, device signed blobs). An option was added to disable signature checks, because that could be useful for people trying to import unsigned stuff. --- Source/Core/Core/ConfigManager.cpp | 2 + Source/Core/Core/ConfigManager.h | 2 + Source/Core/Core/IOS/ES/ES.cpp | 137 +++++++++++++++++++++++++++++ Source/Core/Core/IOS/ES/ES.h | 18 ++++ 4 files changed, 159 insertions(+) diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 7d10bf17c7..86d499c4de 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -286,6 +286,7 @@ void SConfig::SaveCoreSettings(IniFile& ini) core->Set("PerfMapDir", m_perfDir); core->Set("EnableCustomRTC", bEnableCustomRTC); core->Set("CustomRTCValue", m_customRTCValue); + core->Set("EnableSignatureChecks", m_enable_signature_checks); } void SConfig::SaveMovieSettings(IniFile& ini) @@ -608,6 +609,7 @@ void SConfig::LoadCoreSettings(IniFile& ini) core->Get("EnableCustomRTC", &bEnableCustomRTC, false); // Default to seconds between 1.1.1970 and 1.1.2000 core->Get("CustomRTCValue", &m_customRTCValue, 946684800); + core->Get("EnableSignatureChecks", &m_enable_signature_checks, true); } void SConfig::LoadMovieSettings(IniFile& ini) diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 69947125fc..9a97b68df0 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -168,6 +168,8 @@ struct SConfig : NonCopyable std::set> m_usb_passthrough_devices; bool IsUSBDeviceWhitelisted(std::pair vid_pid) const; + bool m_enable_signature_checks = true; + // SYSCONF settings int m_sensor_bar_position = 0x01; int m_sensor_bar_sensitivity = 0x03; diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index c6930e1efc..9a6444d362 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -11,15 +11,20 @@ #include #include +#include + #include "Common/ChunkFile.h" #include "Common/File.h" #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/NandPaths.h" +#include "Common/ScopeGuard.h" +#include "Common/StringUtil.h" #include "Core/ConfigManager.h" #include "Core/HW/Memmap.h" #include "Core/IOS/ES/Formats.h" +#include "Core/IOS/IOSC.h" #include "DiscIO/NANDContentLoader.h" namespace IOS @@ -760,6 +765,138 @@ bool ES::IsActiveTitlePermittedByTicket(const u8* ticket_view) const Common::swap32(ticket_view + offsetof(IOS::ES::TicketView, permitted_title_id)); return title_identifier && (title_identifier & ~permitted_title_mask) == permitted_title_id; } + +bool ES::IsIssuerCorrect(VerifyContainerType type, const IOS::ES::CertReader& issuer_cert) const +{ + switch (type) + { + case VerifyContainerType::TMD: + return issuer_cert.GetName().compare(0, 2, "CP") == 0; + case VerifyContainerType::Ticket: + return issuer_cert.GetName().compare(0, 2, "XS") == 0; + case VerifyContainerType::Device: + return issuer_cert.GetName().compare(0, 2, "MS") == 0; + default: + return false; + } +} + +ReturnCode ES::WriteNewCertToStore(const IOS::ES::CertReader& cert) +{ + const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys"; + // The certificate store file may not exist, so we use a+b and not r+b here. + File::IOFile store_file{store_path, "a+b"}; + if (!store_file) + return ES_EIO; + + // Read the current store to determine if the new cert needs to be written. + const u64 file_size = store_file.GetSize(); + if (file_size != 0) + { + std::vector certs_bytes(file_size); + if (!store_file.ReadBytes(certs_bytes.data(), certs_bytes.size())) + return ES_SHORT_READ; + + const std::map certs = IOS::ES::ParseCertChain(certs_bytes); + // The cert is already present in the store. Nothing to do. + if (certs.find(cert.GetName()) != certs.end()) + return IPC_SUCCESS; + } + + // Otherwise, write the new cert at the end of the store. + // When opening a file in read-write mode, a seek is required before a write. + store_file.Seek(0, SEEK_END); + if (!store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size())) + return ES_EIO; + return IPC_SUCCESS; +} + +ReturnCode ES::VerifyContainer(VerifyContainerType type, VerifyMode mode, + const IOS::ES::SignedBlobReader& signed_blob, + const std::vector& cert_chain, u32 iosc_handle) +{ + if (!SConfig::GetInstance().m_enable_signature_checks) + return IPC_SUCCESS; + + if (!signed_blob.IsSignatureValid()) + return ES_EINVAL; + + // A blob should have exactly 3 parent issuers. + // Example for a ticket: "Root-CA00000001-XS00000003" => {"Root", "CA00000001", "XS00000003"} + const std::string issuer = signed_blob.GetIssuer(); + const std::vector parents = SplitString(issuer, '-'); + if (parents.size() != 3) + return ES_EINVAL; + + // Find the direct issuer and the CA certificates for the blob. + const std::map certs = IOS::ES::ParseCertChain(cert_chain); + const auto issuer_cert_iterator = certs.find(parents[2]); + const auto ca_cert_iterator = certs.find(parents[1]); + if (issuer_cert_iterator == certs.end() || ca_cert_iterator == certs.end()) + return ES_UNKNOWN_ISSUER; + const IOS::ES::CertReader& issuer_cert = issuer_cert_iterator->second; + const IOS::ES::CertReader& ca_cert = ca_cert_iterator->second; + + // Some blobs can only be signed by specific certificates. + if (!IsIssuerCorrect(type, issuer_cert)) + return ES_EINVAL; + + // Verify the whole cert chain using IOSC. + // IOS assumes that the CA cert will always be signed by the root certificate, + // and that the issuer is signed by the CA. + IOSC& iosc = m_ios.GetIOSC(); + IOSC::Handle handle; + + // Create and initialise a handle for the CA cert and the issuer cert. + ReturnCode ret = iosc.CreateObject(&handle, IOSC::TYPE_PUBLIC_KEY, IOSC::SUBTYPE_RSA2048, PID_ES); + if (ret != IPC_SUCCESS) + return ret; + Common::ScopeGuard ca_guard{[&] { iosc.DeleteObject(handle, PID_ES); }}; + ret = iosc.ImportCertificate(ca_cert.GetBytes().data(), IOSC::HANDLE_ROOT_KEY, handle, PID_ES); + if (ret != IPC_SUCCESS) + return ret; + + IOSC::Handle issuer_handle; + const IOSC::ObjectSubType subtype = + type == VerifyContainerType::Device ? IOSC::SUBTYPE_ECC233 : IOSC::SUBTYPE_RSA2048; + ret = iosc.CreateObject(&issuer_handle, IOSC::TYPE_PUBLIC_KEY, subtype, PID_ES); + if (ret != IPC_SUCCESS) + return ret; + Common::ScopeGuard issuer_guard{[&] { iosc.DeleteObject(issuer_handle, PID_ES); }}; + ret = iosc.ImportCertificate(issuer_cert.GetBytes().data(), handle, issuer_handle, PID_ES); + if (ret != IPC_SUCCESS) + return ret; + + // Calculate the SHA1 of the signed blob. + const size_t skip = type == VerifyContainerType::Device ? offsetof(SignatureECC, issuer) : + offsetof(SignatureRSA2048, issuer); + std::array sha1; + mbedtls_sha1(signed_blob.GetBytes().data() + skip, signed_blob.GetBytes().size() - skip, + sha1.data()); + + // Verify the signature. + const std::vector signature = signed_blob.GetSignatureData(); + ret = iosc.VerifyPublicKeySign(sha1, issuer_handle, signature.data(), PID_ES); + if (ret != IPC_SUCCESS) + return ret; + + if (mode == VerifyMode::UpdateCertStore) + { + ret = WriteNewCertToStore(issuer_cert); + if (ret != IPC_SUCCESS) + ERROR_LOG(IOS_ES, "VerifyContainer: Writing the issuer cert failed with return code %d", ret); + + ret = WriteNewCertToStore(ca_cert); + if (ret != IPC_SUCCESS) + ERROR_LOG(IOS_ES, "VerifyContainer: Writing the CA cert failed with return code %d", ret); + } + + // Import the signed blob to iosc_handle (if a handle was passed to us). + if (ret == IPC_SUCCESS && iosc_handle) + ret = iosc.ImportCertificate(signed_blob.GetBytes().data(), issuer_handle, iosc_handle, PID_ES); + + return ret; +} } // namespace Device } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 5115f9d6b6..52b7b7612c 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -306,6 +306,24 @@ private: ReturnCode CheckStreamKeyPermissions(u32 uid, const u8* ticket_view, const IOS::ES::TMDReader& tmd) const; + enum class VerifyContainerType + { + TMD, + Ticket, + Device, + }; + enum class VerifyMode + { + // Whether or not new certificates should be added to the certificate store (/sys/cert.sys). + DoNotUpdateCertStore, + UpdateCertStore, + }; + bool IsIssuerCorrect(VerifyContainerType type, const IOS::ES::CertReader& issuer_cert) const; + ReturnCode WriteNewCertToStore(const IOS::ES::CertReader& cert); + ReturnCode VerifyContainer(VerifyContainerType type, VerifyMode mode, + const IOS::ES::SignedBlobReader& signed_blob, + const std::vector& cert_chain, u32 iosc_handle = 0); + // Start a title import. bool InitImport(u64 title_id); // Clean up the import content directory and move it back to /title. From f3bf1d626c7d370fe859d51af6165cadad814b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 11 Jun 2017 22:01:41 +0200 Subject: [PATCH 03/12] IOS/ES: Add helper function ReadCertStore --- Source/Core/Core/IOS/ES/ES.cpp | 13 +++++++++++++ Source/Core/Core/IOS/ES/ES.h | 1 + 2 files changed, 14 insertions(+) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 9a6444d362..0c029ede92 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -781,6 +781,19 @@ bool ES::IsIssuerCorrect(VerifyContainerType type, const IOS::ES::CertReader& is } } +ReturnCode ES::ReadCertStore(std::vector* buffer) const +{ + const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys"; + File::IOFile store_file{store_path, "rb"}; + if (!store_file) + return FS_ENOENT; + + buffer->resize(store_file.GetSize()); + if (!store_file.ReadBytes(buffer->data(), buffer->size())) + return ES_SHORT_READ; + return IPC_SUCCESS; +} + ReturnCode ES::WriteNewCertToStore(const IOS::ES::CertReader& cert) { const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys"; diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 52b7b7612c..8b18811ac1 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -319,6 +319,7 @@ private: UpdateCertStore, }; bool IsIssuerCorrect(VerifyContainerType type, const IOS::ES::CertReader& issuer_cert) const; + ReturnCode ReadCertStore(std::vector* buffer) const; ReturnCode WriteNewCertToStore(const IOS::ES::CertReader& cert); ReturnCode VerifyContainer(VerifyContainerType type, VerifyMode mode, const IOS::ES::SignedBlobReader& signed_blob, From 07d83ada39a0d14194bc551868f2957694c889a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 11 Jun 2017 21:36:41 +0200 Subject: [PATCH 04/12] IOS/ES: Create missing directories at boot Something that IOS does and that Dolphin doesn't, for whatever reason. --- Source/Core/Core/IOS/ES/ES.cpp | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 0c029ede92..d0d3542cc5 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -40,8 +40,46 @@ static TitleContext s_title_context; // Title to launch after IOS has been reset and reloaded (similar to /sys/launch.sys). static u64 s_title_to_launch; +struct DirectoryToCreate +{ + const char* path; + u32 attributes; + OpenMode owner_perm; + OpenMode group_perm; + OpenMode other_perm; +}; + +constexpr std::array s_directories_to_create = {{ + {"/sys", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE}, + {"/ticket", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE}, + {"/title", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_READ}, + {"/shared1", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE}, + {"/shared2", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW}, + {"/tmp", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW}, + {"/import", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE}, + {"/meta", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW}, + {"/wfs", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE, OpenMode::IOS_OPEN_NONE}, +}}; + ES::ES(Kernel& ios, const std::string& device_name) : Device(ios, device_name) { + for (const auto& directory : s_directories_to_create) + { + const std::string path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + directory.path; + + // Create the directory if it does not exist. + if (File::IsDirectory(path)) + continue; + + File::CreateFullPath(path); + if (File::CreateDir(path)) + INFO_LOG(IOS_ES, "Created %s (at %s)", directory.path, path.c_str()); + else + ERROR_LOG(IOS_ES, "Failed to create %s (at %s)", directory.path, path.c_str()); + + // TODO: Set permissions. + } + FinishAllStaleImports(); s_content_file = ""; From 719af1aff482cf1ae37bccedf071962ffad761c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 11 Jun 2017 18:35:24 +0200 Subject: [PATCH 05/12] IOS/ES: Verify containers in ImportTicket --- Source/Core/Core/IOS/ES/ES.h | 2 +- Source/Core/Core/IOS/ES/TitleManagement.cpp | 11 +++++++++-- Source/Core/UICommon/WiiUtils.cpp | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 8b18811ac1..27cc16bca0 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -112,7 +112,7 @@ public: std::vector> GetSharedContents() const; // Title management - ReturnCode ImportTicket(const std::vector& ticket_bytes); + ReturnCode ImportTicket(const std::vector& ticket_bytes, const std::vector& cert_chain); ReturnCode ImportTmd(Context& context, const std::vector& tmd_bytes); ReturnCode ImportTitleInit(Context& context, const std::vector& tmd_bytes); ReturnCode ImportContentBegin(Context& context, u64 title_id, u32 content_id); diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index f5206ef1a1..dacefe9db9 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -46,7 +46,7 @@ static ReturnCode WriteTicket(const IOS::ES::TicketReader& ticket) return ticket_file.WriteBytes(raw_ticket.data(), raw_ticket.size()) ? IPC_SUCCESS : ES_EIO; } -ReturnCode ES::ImportTicket(const std::vector& ticket_bytes) +ReturnCode ES::ImportTicket(const std::vector& ticket_bytes, const std::vector& cert_chain) { IOS::ES::TicketReader ticket{ticket_bytes}; if (!ticket.IsValid()) @@ -70,6 +70,11 @@ ReturnCode ES::ImportTicket(const std::vector& ticket_bytes) } } + const ReturnCode verify_ret = + VerifyContainer(VerifyContainerType::Ticket, VerifyMode::UpdateCertStore, ticket, cert_chain); + if (verify_ret != IPC_SUCCESS) + return verify_ret; + const ReturnCode write_ret = WriteTicket(ticket); if (write_ret != IPC_SUCCESS) return write_ret; @@ -85,7 +90,9 @@ IPCCommandResult ES::ImportTicket(const IOCtlVRequest& request) std::vector bytes(request.in_vectors[0].size); Memory::CopyFromEmu(bytes.data(), request.in_vectors[0].address, request.in_vectors[0].size); - return GetDefaultReply(ImportTicket(bytes)); + std::vector cert_chain(request.in_vectors[1].size); + Memory::CopyFromEmu(bytes.data(), request.in_vectors[1].address, request.in_vectors[1].size); + return GetDefaultReply(ImportTicket(bytes, cert_chain)); } ReturnCode ES::ImportTmd(Context& context, const std::vector& tmd_bytes) diff --git a/Source/Core/UICommon/WiiUtils.cpp b/Source/Core/UICommon/WiiUtils.cpp index acd4713a33..35e4c22678 100644 --- a/Source/Core/UICommon/WiiUtils.cpp +++ b/Source/Core/UICommon/WiiUtils.cpp @@ -27,7 +27,7 @@ bool InstallWAD(const std::string& wad_path) const auto es = ios.GetES(); IOS::HLE::Device::ES::Context context; - if (es->ImportTicket(wad.GetTicket().GetBytes()) < 0 || + if (es->ImportTicket(wad.GetTicket().GetBytes(), wad.GetCertificateChain()) < 0 || es->ImportTitleInit(context, tmd.GetBytes()) < 0) { PanicAlertT("WAD installation failed: Could not initialise title import."); From 8a49e1f7dba84d41589971965b09b2e03d0f8fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 11 Jun 2017 22:05:58 +0200 Subject: [PATCH 06/12] IOS/ES: Verify containers in ImportTitleInit --- Source/Core/Core/IOS/ES/ES.h | 3 +- Source/Core/Core/IOS/ES/TitleManagement.cpp | 34 ++++++++++++++++++--- Source/Core/UICommon/WiiUtils.cpp | 2 +- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 27cc16bca0..0cb7deadef 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -114,7 +114,8 @@ public: // Title management ReturnCode ImportTicket(const std::vector& ticket_bytes, const std::vector& cert_chain); ReturnCode ImportTmd(Context& context, const std::vector& tmd_bytes); - ReturnCode ImportTitleInit(Context& context, const std::vector& tmd_bytes); + ReturnCode ImportTitleInit(Context& context, const std::vector& tmd_bytes, + const std::vector& cert_chain); ReturnCode ImportContentBegin(Context& context, u64 title_id, u32 content_id); ReturnCode ImportContentData(Context& context, u32 content_fd, const u8* data, u32 data_size); ReturnCode ImportContentEnd(Context& context, u32 content_fd); diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index dacefe9db9..053d65c8cc 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -123,7 +123,8 @@ IPCCommandResult ES::ImportTmd(Context& context, const IOCtlVRequest& request) return GetDefaultReply(ImportTmd(context, tmd)); } -ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_bytes) +ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_bytes, + const std::vector& cert_chain) { INFO_LOG(IOS_ES, "ImportTitleInit"); context.title_import.tmd.SetBytes(tmd_bytes); @@ -136,11 +137,34 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_byte // Finish a previous import (if it exists). FinishStaleImport(context.title_import.tmd.GetTitleId()); + ReturnCode ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, + context.title_import.tmd, cert_chain); + if (ret != IPC_SUCCESS) + { + context.title_import.tmd.SetBytes({}); + return ret; + } + + const auto ticket = DiscIO::FindSignedTicket(context.title_import.tmd.GetTitleId()); + if (!ticket.IsValid()) + return ES_NO_TICKET; + + std::vector cert_store; + ret = ReadCertStore(&cert_store); + if (ret != IPC_SUCCESS) + return ret; + + ret = VerifyContainer(VerifyContainerType::Ticket, VerifyMode::DoNotUpdateCertStore, ticket, + cert_store); + if (ret != IPC_SUCCESS) + { + context.title_import.tmd.SetBytes({}); + return ret; + } + if (!InitImport(context.title_import.tmd.GetTitleId())) return ES_EIO; - // TODO: check and use the other vectors. - return IPC_SUCCESS; } @@ -154,7 +178,9 @@ IPCCommandResult ES::ImportTitleInit(Context& context, const IOCtlVRequest& requ std::vector tmd(request.in_vectors[0].size); Memory::CopyFromEmu(tmd.data(), request.in_vectors[0].address, request.in_vectors[0].size); - return GetDefaultReply(ImportTitleInit(context, tmd)); + std::vector certs(request.in_vectors[1].size); + Memory::CopyFromEmu(certs.data(), request.in_vectors[1].address, request.in_vectors[1].size); + return GetDefaultReply(ImportTitleInit(context, tmd, certs)); } ReturnCode ES::ImportContentBegin(Context& context, u64 title_id, u32 content_id) diff --git a/Source/Core/UICommon/WiiUtils.cpp b/Source/Core/UICommon/WiiUtils.cpp index 35e4c22678..d576dbcdd0 100644 --- a/Source/Core/UICommon/WiiUtils.cpp +++ b/Source/Core/UICommon/WiiUtils.cpp @@ -28,7 +28,7 @@ bool InstallWAD(const std::string& wad_path) IOS::HLE::Device::ES::Context context; if (es->ImportTicket(wad.GetTicket().GetBytes(), wad.GetCertificateChain()) < 0 || - es->ImportTitleInit(context, tmd.GetBytes()) < 0) + es->ImportTitleInit(context, tmd.GetBytes(), wad.GetCertificateChain()) < 0) { PanicAlertT("WAD installation failed: Could not initialise title import."); return false; From 54025da00f80fe78b1ecba133a97c557e2e96491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 11 Jun 2017 22:08:27 +0200 Subject: [PATCH 07/12] IOS/ES: Add note about verification in DiVerify We probably don't want to verify containers there because it might result in patched/custom games failing the check. --- Source/Core/Core/IOS/ES/ES.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index d0d3542cc5..1053c0dbc1 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -653,6 +653,9 @@ s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& tic if (!File::Exists(tmd_path)) { + // XXX: We are supposed to verify the TMD and ticket here, but cannot because + // this may cause issues with custom/patched games. + File::IOFile tmd_file(tmd_path, "wb"); const std::vector& tmd_bytes = tmd.GetBytes(); if (!tmd_file.WriteBytes(tmd_bytes.data(), tmd_bytes.size())) From 36c5caacf4d38c60a6385960a5e7a6ab8607e117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 11 Jun 2017 22:10:33 +0200 Subject: [PATCH 08/12] IOS/ES: Verify containers in ImportTmd --- Source/Core/Core/IOS/ES/TitleManagement.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 053d65c8cc..37c5472b8f 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -100,10 +100,23 @@ ReturnCode ES::ImportTmd(Context& context, const std::vector& tmd_bytes) // Ioctlv 0x2b writes the TMD to /tmp/title.tmd (for imports) and doesn't seem to write it // to either /import or /title. So here we simply have to set the import TMD. context.title_import.tmd.SetBytes(tmd_bytes); - // TODO: validate TMDs and return the proper error code (-1027) if the signature type is invalid. if (!context.title_import.tmd.IsValid()) return ES_EINVAL; + std::vector cert_store; + ReturnCode ret = ReadCertStore(&cert_store); + if (ret != IPC_SUCCESS) + return ret; + + ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, + context.title_import.tmd, cert_store); + if (ret != IPC_SUCCESS) + { + // Reset the import context so that further calls consider the state as invalid. + context.title_import.tmd.SetBytes({}); + return ret; + } + if (!InitImport(context.title_import.tmd.GetTitleId())) return ES_EIO; From d77b7ac90e746ffc5fca6d56a6b51542a010780d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 11 Jun 2017 22:13:17 +0200 Subject: [PATCH 09/12] IOS/ES: Verify containers in SetUpStreamKey --- Source/Core/Core/IOS/ES/ES.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 1053c0dbc1..a1a7d78054 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -738,6 +738,19 @@ ReturnCode ES::SetUpStreamKey(const u32 uid, const u8* ticket_view, const IOS::E if (ticket_bytes.empty()) return ES_NO_TICKET; + std::vector cert_store; + ret = ReadCertStore(&cert_store); + if (ret != IPC_SUCCESS) + return ret; + + ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, tmd, cert_store); + if (ret != IPC_SUCCESS) + return ret; + ret = VerifyContainer(VerifyContainerType::Ticket, VerifyMode::UpdateCertStore, installed_ticket, + cert_store); + if (ret != IPC_SUCCESS) + return ret; + // Create the handle and return it. std::array iv{}; std::memcpy(iv.data(), &title_id, sizeof(title_id)); From c08806d1076a16598780920a8b773b991396a4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 11 Jun 2017 22:16:05 +0200 Subject: [PATCH 10/12] IOS/ES: Verify containers in GetTMDStoredContents --- Source/Core/Core/IOS/ES/TitleInformation.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/IOS/ES/TitleInformation.cpp b/Source/Core/Core/IOS/ES/TitleInformation.cpp index 4312c9d2c3..1517f367ec 100644 --- a/Source/Core/Core/IOS/ES/TitleInformation.cpp +++ b/Source/Core/Core/IOS/ES/TitleInformation.cpp @@ -97,7 +97,21 @@ IPCCommandResult ES::GetTMDStoredContents(const IOCtlVRequest& request) std::vector tmd_bytes(request.in_vectors[0].size); Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size()); - return GetStoredContents(IOS::ES::TMDReader{std::move(tmd_bytes)}, request); + + const IOS::ES::TMDReader tmd{std::move(tmd_bytes)}; + if (!tmd.IsValid()) + return GetDefaultReply(ES_EINVAL); + + std::vector cert_store; + ReturnCode ret = ReadCertStore(&cert_store); + if (ret != IPC_SUCCESS) + return GetDefaultReply(ret); + + ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, tmd, cert_store); + if (ret != IPC_SUCCESS) + return GetDefaultReply(ret); + + return GetStoredContents(tmd, request); } IPCCommandResult ES::GetTitleCount(const std::vector& titles, const IOCtlVRequest& request) From 6503a9f53879f0a82ece1299397106b2ba088157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Mon, 12 Jun 2017 02:01:10 +0200 Subject: [PATCH 11/12] Allow the user to ignore signature issues during WAD import Improves usability with signature checks. --- Source/Core/UICommon/WiiUtils.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Source/Core/UICommon/WiiUtils.cpp b/Source/Core/UICommon/WiiUtils.cpp index d576dbcdd0..994079f623 100644 --- a/Source/Core/UICommon/WiiUtils.cpp +++ b/Source/Core/UICommon/WiiUtils.cpp @@ -5,6 +5,7 @@ #include "UICommon/WiiUtils.h" #include "Common/CommonTypes.h" #include "Common/MsgHandler.h" +#include "Core/ConfigManager.h" #include "Core/IOS/ES/ES.h" #include "Core/IOS/ES/Formats.h" #include "Core/IOS/IOS.h" @@ -27,12 +28,23 @@ bool InstallWAD(const std::string& wad_path) const auto es = ios.GetES(); IOS::HLE::Device::ES::Context context; - if (es->ImportTicket(wad.GetTicket().GetBytes(), wad.GetCertificateChain()) < 0 || - es->ImportTitleInit(context, tmd.GetBytes(), wad.GetCertificateChain()) < 0) + IOS::HLE::ReturnCode ret; + const bool checks_enabled = SConfig::GetInstance().m_enable_signature_checks; + while ((ret = es->ImportTicket(wad.GetTicket().GetBytes(), wad.GetCertificateChain())) < 0 || + (ret = es->ImportTitleInit(context, tmd.GetBytes(), wad.GetCertificateChain())) < 0) { + if (checks_enabled && ret == IOS::HLE::IOSC_FAIL_CHECKVALUE && + AskYesNoT("This WAD has not been signed by Nintendo. Continue to import?")) + { + SConfig::GetInstance().m_enable_signature_checks = false; + continue; + } + + SConfig::GetInstance().m_enable_signature_checks = checks_enabled; PanicAlertT("WAD installation failed: Could not initialise title import."); return false; } + SConfig::GetInstance().m_enable_signature_checks = checks_enabled; const bool contents_imported = [&]() { const u64 title_id = tmd.GetTitleId(); From 6249244c90a2859ec8c7a5981d7dafcc6d166db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 16 Jun 2017 10:10:23 +0200 Subject: [PATCH 12/12] IOS/ES: Fix formatting --- Source/Core/Core/IOS/ES/TitleManagement.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 37c5472b8f..cd0563c5ff 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -37,7 +37,6 @@ static ReturnCode WriteTicket(const IOS::ES::TicketReader& ticket) const std::string ticket_path = Common::GetTicketFileName(title_id, Common::FROM_SESSION_ROOT); File::CreateFullPath(ticket_path); - File::IOFile ticket_file(ticket_path, "wb"); if (!ticket_file) return ES_EIO;