Merge pull request #5373 from leoetlino/delete-ticket

IOS/ES: Fix the implementation of ES_DeleteTicket
This commit is contained in:
Léo Lam 2017-05-06 13:59:11 +02:00 committed by GitHub
commit f4179f45b4
4 changed files with 77 additions and 49 deletions

View File

@ -239,19 +239,7 @@ void TicketReader::SetBytes(std::vector<u8>&& bytes)
bool TicketReader::IsValid() const bool TicketReader::IsValid() const
{ {
// Too small for the signature type. return m_bytes.size() % sizeof(Ticket) == 0;
if (m_bytes.size() < sizeof(u32))
return false;
u32 ticket_offset = GetOffset();
if (ticket_offset == 0)
return false;
// Too small for the ticket itself.
if (m_bytes.size() < ticket_offset + sizeof(Ticket))
return false;
return true;
} }
void TicketReader::DoState(PointerWrap& p) void TicketReader::DoState(PointerWrap& p)
@ -259,23 +247,9 @@ void TicketReader::DoState(PointerWrap& p)
p.Do(m_bytes); p.Do(m_bytes);
} }
u32 TicketReader::GetNumberOfTickets() const size_t TicketReader::GetNumberOfTickets() const
{ {
return static_cast<u32>(m_bytes.size() / (GetOffset() + sizeof(Ticket))); return m_bytes.size() / sizeof(Ticket);
}
u32 TicketReader::GetOffset() const
{
u32 signature_type = Common::swap32(m_bytes.data());
if (signature_type == 0x10000) // RSA4096
return 576;
if (signature_type == 0x10001) // RSA2048
return 320;
if (signature_type == 0x10002) // ECDSA
return 128;
ERROR_LOG(COMMON, "Invalid ticket signature type: %08x", signature_type);
return 0;
} }
const std::vector<u8>& TicketReader::GetRawTicket() const const std::vector<u8>& TicketReader::GetRawTicket() const
@ -286,7 +260,7 @@ const std::vector<u8>& TicketReader::GetRawTicket() const
std::vector<u8> TicketReader::GetRawTicketView(u32 ticket_num) const std::vector<u8> TicketReader::GetRawTicketView(u32 ticket_num) const
{ {
// A ticket view is composed of a version + part of a ticket starting from the ticket_id field. // A ticket view is composed of a version + part of a ticket starting from the ticket_id field.
const auto ticket_start = m_bytes.cbegin() + GetOffset() + sizeof(Ticket) * ticket_num; const auto ticket_start = m_bytes.cbegin() + sizeof(Ticket) * ticket_num;
const auto view_start = ticket_start + offsetof(Ticket, ticket_id); const auto view_start = ticket_start + offsetof(Ticket, ticket_id);
// Copy the ticket version to the buffer (a single byte extended to 4). // Copy the ticket version to the buffer (a single byte extended to 4).
@ -303,32 +277,47 @@ std::vector<u8> TicketReader::GetRawTicketView(u32 ticket_num) const
u32 TicketReader::GetDeviceId() const u32 TicketReader::GetDeviceId() const
{ {
return Common::swap32(m_bytes.data() + GetOffset() + offsetof(Ticket, device_id)); return Common::swap32(m_bytes.data() + offsetof(Ticket, device_id));
} }
u64 TicketReader::GetTitleId() const u64 TicketReader::GetTitleId() const
{ {
return Common::swap64(m_bytes.data() + GetOffset() + offsetof(Ticket, title_id)); return Common::swap64(m_bytes.data() + offsetof(Ticket, title_id));
} }
std::vector<u8> TicketReader::GetTitleKey() const std::vector<u8> TicketReader::GetTitleKey() const
{ {
u8 iv[16] = {}; u8 iv[16] = {};
std::copy_n(&m_bytes[GetOffset() + offsetof(Ticket, title_id)], sizeof(Ticket::title_id), iv); std::copy_n(&m_bytes[offsetof(Ticket, title_id)], sizeof(Ticket::title_id), iv);
auto common_key_handle = m_bytes.at(GetOffset() + offsetof(Ticket, common_key_index)) == 0 ? auto common_key_handle = m_bytes.at(offsetof(Ticket, common_key_index)) == 0 ?
HLE::IOSC::HANDLE_COMMON_KEY : HLE::IOSC::HANDLE_COMMON_KEY :
HLE::IOSC::HANDLE_NEW_COMMON_KEY; HLE::IOSC::HANDLE_NEW_COMMON_KEY;
std::vector<u8> key(16); std::vector<u8> key(16);
HLE::IOSC iosc; HLE::IOSC iosc;
iosc.Decrypt(common_key_handle, iv, &m_bytes[GetOffset() + offsetof(Ticket, title_key)], 16, iosc.Decrypt(common_key_handle, iv, &m_bytes[offsetof(Ticket, title_key)], 16, key.data(),
key.data(), HLE::PID_ES); HLE::PID_ES);
return key; return key;
} }
void TicketReader::DeleteTicket(u64 ticket_id_to_delete)
{
std::vector<u8> new_ticket;
const size_t num_tickets = GetNumberOfTickets();
for (size_t i = 0; i < num_tickets; ++i)
{
const auto ticket_start = m_bytes.cbegin() + sizeof(Ticket) * i;
const u64 ticket_id = Common::swap64(&*ticket_start + offsetof(Ticket, ticket_id));
if (ticket_id != ticket_id_to_delete)
new_ticket.insert(new_ticket.end(), ticket_start, ticket_start + sizeof(Ticket));
}
m_bytes = std::move(new_ticket);
}
s32 TicketReader::Unpersonalise() s32 TicketReader::Unpersonalise()
{ {
const auto ticket_begin = m_bytes.begin() + GetOffset(); const auto ticket_begin = m_bytes.begin();
// IOS uses IOSC to compute an AES key from the peer public key and the device's private ECC key, // IOS uses IOSC to compute an AES key from the peer public key and the device's private ECC key,
// which is used the decrypt the title key. The IV is the ticket ID (8 bytes), zero extended. // which is used the decrypt the title key. The IV is the ticket ID (8 bytes), zero extended.

View File

@ -97,8 +97,14 @@ struct TicketView
}; };
static_assert(sizeof(TicketView) == 0xd8, "TicketView has the wrong size"); static_assert(sizeof(TicketView) == 0xd8, "TicketView has the wrong size");
// This structure is used for (signed) tickets. Technically, there are other types of tickets
// (RSA4096, ECDSA, ...). However, only RSA2048 tickets have ever been seen and these are also
// the only ticket type that is supported by the Wii's IOS.
struct Ticket struct Ticket
{ {
u32 signature_type;
u8 signature[256];
u8 unused[60];
u8 signature_issuer[0x40]; u8 signature_issuer[0x40];
u8 server_public_key[0x3c]; u8 server_public_key[0x3c];
u8 version; u8 version;
@ -118,7 +124,7 @@ struct Ticket
u8 content_access_permissions[0x40]; u8 content_access_permissions[0x40];
TimeLimit time_limits[8]; TimeLimit time_limits[8];
}; };
static_assert(sizeof(Ticket) == 356, "Ticket has the wrong size"); static_assert(sizeof(Ticket) == 0x2A4, "Ticket has the wrong size");
#pragma pack(pop) #pragma pack(pop)
class TMDReader final class TMDReader final
@ -175,8 +181,7 @@ public:
void DoState(PointerWrap& p); void DoState(PointerWrap& p);
const std::vector<u8>& GetRawTicket() const; const std::vector<u8>& GetRawTicket() const;
u32 GetNumberOfTickets() const; size_t GetNumberOfTickets() const;
u32 GetOffset() const;
// Returns a "raw" ticket view, without byte swapping. Intended for use from ES. // Returns a "raw" ticket view, without byte swapping. Intended for use from ES.
// Theoretically, a ticket file can contain one or more tickets. In practice, most (all?) // Theoretically, a ticket file can contain one or more tickets. In practice, most (all?)
@ -188,6 +193,9 @@ public:
u64 GetTitleId() const; u64 GetTitleId() const;
std::vector<u8> GetTitleKey() const; std::vector<u8> GetTitleKey() const;
// Deletes a ticket with the given ticket ID from the internal buffer.
void DeleteTicket(u64 ticket_id);
// Decrypts the title key field for a "personalised" ticket -- one that is device-specific // Decrypts the title key field for a "personalised" ticket -- one that is device-specific
// and has a title key that must be decrypted first. // and has a title key that must be decrypted first.
s32 Unpersonalise(); s32 Unpersonalise();

View File

@ -6,6 +6,7 @@
#include <algorithm> #include <algorithm>
#include <cinttypes> #include <cinttypes>
#include <cstddef>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -335,15 +336,45 @@ IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request)
IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request) IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request)
{ {
if (!request.HasNumberOfValidVectors(1, 0)) if (!request.HasNumberOfValidVectors(1, 0) ||
request.in_vectors[0].size != sizeof(IOS::ES::TicketView))
{
return GetDefaultReply(ES_EINVAL);
}
const u64 title_id =
Memory::Read_U64(request.in_vectors[0].address + offsetof(IOS::ES::TicketView, title_id));
if (!CanDeleteTitle(title_id))
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); auto ticket = DiscIO::FindSignedTicket(title_id);
INFO_LOG(IOS_ES, "IOCTL_ES_DELETETICKET: title: %08x/%08x", (u32)(TitleID >> 32), (u32)TitleID); if (!ticket.IsValid())
return GetDefaultReply(FS_ENOENT);
// Presumably return -1017 when delete fails const u64 ticket_id =
if (!File::Delete(Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT))) Memory::Read_U64(request.in_vectors[0].address + offsetof(IOS::ES::TicketView, ticket_id));
return GetDefaultReply(ES_EINVAL); ticket.DeleteTicket(ticket_id);
const std::vector<u8>& new_ticket = ticket.GetRawTicket();
const std::string ticket_path = Common::GetTicketFileName(title_id, Common::FROM_SESSION_ROOT);
{
File::IOFile ticket_file(ticket_path, "wb");
if (!ticket_file || !ticket_file.WriteBytes(new_ticket.data(), new_ticket.size()))
return GetDefaultReply(ES_EIO);
}
// Delete the ticket file if it is now empty.
if (new_ticket.empty())
File::Delete(ticket_path);
// Delete the ticket directory if it is now empty.
const std::string ticket_parent_dir =
Common::RootUserPath(Common::FROM_CONFIGURED_ROOT) +
StringFromFormat("/ticket/%08x", static_cast<u32>(title_id >> 32));
const auto ticket_parent_dir_entries = File::ScanDirectoryTree(ticket_parent_dir, false);
if (ticket_parent_dir_entries.children.empty())
File::DeleteDir(ticket_parent_dir);
return GetDefaultReply(IPC_SUCCESS); return GetDefaultReply(IPC_SUCCESS);
} }

View File

@ -48,7 +48,7 @@ IPCCommandResult ES::GetTicketViewCount(const IOCtlVRequest& request)
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(TitleID); const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(TitleID);
u32 view_count = ticket.IsValid() ? ticket.GetNumberOfTickets() : 0; u32 view_count = ticket.IsValid() ? static_cast<u32>(ticket.GetNumberOfTickets()) : 0;
if (ShouldReturnFakeViewsForIOSes(TitleID, GetTitleContext())) if (ShouldReturnFakeViewsForIOSes(TitleID, GetTitleContext()))
{ {
@ -75,7 +75,7 @@ IPCCommandResult ES::GetTicketViews(const IOCtlVRequest& request)
if (ticket.IsValid()) if (ticket.IsValid())
{ {
u32 number_of_views = std::min(maxViews, ticket.GetNumberOfTickets()); u32 number_of_views = std::min(maxViews, static_cast<u32>(ticket.GetNumberOfTickets()));
for (u32 view = 0; view < number_of_views; ++view) for (u32 view = 0; view < number_of_views; ++view)
{ {
const std::vector<u8> ticket_view = ticket.GetRawTicketView(view); const std::vector<u8> ticket_view = ticket.GetRawTicketView(view);
@ -235,7 +235,7 @@ IPCCommandResult ES::DIGetTicketView(const IOCtlVRequest& request)
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
} }
const bool has_ticket_vector = request.in_vectors[0].size == 0x2A4; const bool has_ticket_vector = request.in_vectors[0].size == sizeof(IOS::ES::Ticket);
// This ioctlv takes either a signed ticket or no ticket, in which case the ticket size must be 0. // This ioctlv takes either a signed ticket or no ticket, in which case the ticket size must be 0.
if (!has_ticket_vector && request.in_vectors[0].size != 0) if (!has_ticket_vector && request.in_vectors[0].size != 0)