mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-02 02:52:30 +02:00
Merge pull request #5612 from leoetlino/verify-signatures
IOS/ES: Add signature verification
This commit is contained in:
commit
37208d2874
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user