summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorLeo Lam <leolino.lam@gmail.com>2017-06-16 16:36:26 +0200
committerGitHub <noreply@github.com>2017-06-16 16:36:26 +0200
commit37208d2874dad0d0fcef37ad3f970eb0ef747fb0 (patch)
treecd60f16f7343116957115d98db39b7ff22f7dd6b /Source/Core
parent13b1fa2c658c2c0fd841449b54e4509f757cdcbc (diff)
parent6249244c90a2859ec8c7a5981d7dafcc6d166db8 (diff)
Merge pull request #5612 from leoetlino/verify-signatures
IOS/ES: Add signature verification
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/Core/ConfigManager.cpp2
-rw-r--r--Source/Core/Core/ConfigManager.h2
-rw-r--r--Source/Core/Core/IOS/Device.h53
-rw-r--r--Source/Core/Core/IOS/ES/ES.cpp204
-rw-r--r--Source/Core/Core/IOS/ES/ES.h24
-rw-r--r--Source/Core/Core/IOS/ES/TitleInformation.cpp16
-rw-r--r--Source/Core/Core/IOS/ES/TitleManagement.cpp61
-rw-r--r--Source/Core/UICommon/WiiUtils.cpp16
8 files changed, 339 insertions, 39 deletions
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<std::pair<u16, u16>> m_usb_passthrough_devices;
bool IsUSBDeviceWhitelisted(std::pair<u16, u16> 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 <utility>
#include <vector>
+#include <mbedtls/sha1.h>
+
#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<DirectoryToCreate, 9> 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<u8>& 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<u8> 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<u8, 16> 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<u8>* 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<u8> certs_bytes(file_size);
+ if (!store_file.ReadBytes(certs_bytes.data(), certs_bytes.size()))
+ return ES_SHORT_READ;
+
+ const std::map<std::string, IOS::ES::CertReader> 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<u8>& 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<std::string> parents = SplitString(issuer, '-');
+ if (parents.size() != 3)
+ return ES_EINVAL;
+
+ // Find the direct issuer and the CA certificates for the blob.
+ const std::map<std::string, IOS::ES::CertReader> 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<u8, 20> sha1;
+ mbedtls_sha1(signed_blob.GetBytes().data() + skip, signed_blob.GetBytes().size() - skip,
+ sha1.data());
+
+ // Verify the signature.
+ const std::vector<u8> 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<std::array<u8, 20>> GetSharedContents() const;
// Title management
- ReturnCode ImportTicket(const std::vector<u8>& ticket_bytes);
+ ReturnCode ImportTicket(const std::vector<u8>& ticket_bytes, const std::vector<u8>& cert_chain);
ReturnCode ImportTmd(Context& context, const std::vector<u8>& tmd_bytes);
- ReturnCode ImportTitleInit(Context& context, const std::vector<u8>& tmd_bytes);
+ ReturnCode ImportTitleInit(Context& context, const std::vector<u8>& tmd_bytes,
+ const std::vector<u8>& 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<u8>* buffer) const;
+ ReturnCode WriteNewCertToStore(const IOS::ES::CertReader& cert);
+ ReturnCode VerifyContainer(VerifyContainerType type, VerifyMode mode,
+ const IOS::ES::SignedBlobReader& signed_blob,
+ const std::vector<u8>& 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<u8> 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<u8> 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<u64>& 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<u8>& ticket_bytes)
+ReturnCode ES::ImportTicket(const std::vector<u8>& ticket_bytes, const std::vector<u8>& cert_chain)
{
IOS::ES::TicketReader ticket{ticket_bytes};
if (!ticket.IsValid())
@@ -70,6 +69,11 @@ ReturnCode ES::ImportTicket(const std::vector<u8>& 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<u8> 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<u8> 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<u8>& tmd_bytes)
@@ -93,10 +99,23 @@ ReturnCode ES::ImportTmd(Context& context, const std::vector<u8>& 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<u8> 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<u8>& tmd_bytes)
+ReturnCode ES::ImportTitleInit(Context& context, const std::vector<u8>& tmd_bytes,
+ const std::vector<u8>& 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<u8>& 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<u8> 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<u8> 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<u8> 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();