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/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, diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index c6930e1efc..a1a7d78054 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 @@ -35,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 = ""; @@ -610,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())) @@ -692,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)); @@ -760,6 +819,151 @@ 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::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"; + // 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..0cb7deadef 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -112,9 +112,10 @@ 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 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); @@ -306,6 +307,25 @@ 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 ReadCertStore(std::vector* buffer) 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. 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) diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index f5206ef1a1..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; @@ -46,7 +45,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 +69,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 +89,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) @@ -93,10 +99,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; @@ -116,7 +135,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); @@ -129,11 +149,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; } @@ -147,7 +190,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 acd4713a33..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()) < 0 || - es->ImportTitleInit(context, tmd.GetBytes()) < 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();