summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorLeo Lam <leolino.lam@gmail.com>2017-06-08 20:11:14 +0200
committerGitHub <noreply@github.com>2017-06-08 20:11:14 +0200
commit9885a2bb287b32fb248d8a8d9a3aa1697b4e3b0d (patch)
treedf3e5c16a65519324c4ff5c9123e7343622b4402 /Source/Core
parent9ec14eb17f399376bc67be73faa5f3d9493a41de (diff)
parent7b4404c7d56b6d628a6c7b5399ad2dd4c4a0e3d9 (diff)
Merge pull request #5575 from leoetlino/setup-stream-key
IOS/ES: Implement ES_SetUpStreamKey
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/Core/IOS/ES/ES.cpp128
-rw-r--r--Source/Core/Core/IOS/ES/ES.h10
-rw-r--r--Source/Core/Core/IOS/ES/Formats.cpp5
-rw-r--r--Source/Core/Core/IOS/ES/Formats.h17
-rw-r--r--Source/Core/Core/IOS/IOSC.cpp6
5 files changed, 162 insertions, 4 deletions
diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp
index 6023325681..85eb87de8a 100644
--- a/Source/Core/Core/IOS/ES/ES.cpp
+++ b/Source/Core/Core/IOS/ES/ES.cpp
@@ -514,11 +514,12 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request)
case IOCTL_ES_GET_TICKET_FROM_VIEW:
return GetTicketFromView(request);
+ case IOCTL_ES_SET_UP_STREAM_KEY:
+ return SetUpStreamKey(*context, request);
case IOCTL_ES_DELETE_STREAM_KEY:
return DeleteStreamKey(request);
case IOCTL_ES_VERIFYSIGN:
- case IOCTL_ES_UNKNOWN_3C:
case IOCTL_ES_UNKNOWN_41:
case IOCTL_ES_UNKNOWN_42:
PanicAlert("IOS-ES: Unimplemented ioctlv 0x%x (%zu in vectors, %zu io vectors)",
@@ -646,6 +647,118 @@ s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& tic
return IPC_SUCCESS;
}
+constexpr u32 FIRST_PPC_UID = 0x1000;
+
+ReturnCode ES::CheckStreamKeyPermissions(const u32 uid, const u8* ticket_view,
+ const IOS::ES::TMDReader& tmd) const
+{
+ const u32 title_flags = tmd.GetTitleFlags();
+ // Only allow using this function with some titles (WFS titles).
+ // The following is the exact check from IOS. Unfortunately, other than knowing that the
+ // title type is what IOS checks, we don't know much about the constants used here.
+ constexpr u32 WFS_AND_0x4_FLAG = IOS::ES::TITLE_TYPE_0x4 | IOS::ES::TITLE_TYPE_WFS_MAYBE;
+ if ((!(title_flags & IOS::ES::TITLE_TYPE_0x4) && ~(title_flags >> 5) & 1) ||
+ (title_flags & WFS_AND_0x4_FLAG) == WFS_AND_0x4_FLAG)
+ {
+ return ES_EINVAL;
+ }
+
+ // This function can only be used by specific UIDs, depending on the title type.
+ // It cannot be used at all internally, unless the request comes from the WFS process.
+ // It can only be used from the PPC for some title types.
+ // Note: PID 0x19 is used by the WFS modules.
+ if (uid < FIRST_PPC_UID && uid != PID_UNKNOWN)
+ return ES_EINVAL;
+
+ // If the title type is of this specific type, then this function is limited to WFS.
+ if (title_flags & IOS::ES::TITLE_TYPE_WFS_MAYBE && uid != PID_UNKNOWN)
+ return ES_EINVAL;
+
+ const u64 view_title_id = Common::swap64(ticket_view + offsetof(IOS::ES::TicketView, title_id));
+ if (view_title_id != tmd.GetTitleId())
+ return ES_EINVAL;
+
+ // More permission checks.
+ const u32 permitted_title_mask =
+ Common::swap32(ticket_view + offsetof(IOS::ES::TicketView, permitted_title_mask));
+ const u32 permitted_title_id =
+ Common::swap32(ticket_view + offsetof(IOS::ES::TicketView, permitted_title_id));
+ if ((uid == PID_UNKNOWN && (~permitted_title_mask & 0x13) != permitted_title_id) ||
+ !IsActiveTitlePermittedByTicket(ticket_view))
+ {
+ return ES_EACCES;
+ }
+
+ return IPC_SUCCESS;
+}
+
+ReturnCode ES::SetUpStreamKey(const u32 uid, const u8* ticket_view, const IOS::ES::TMDReader& tmd,
+ u32* handle)
+{
+ ReturnCode ret = CheckStreamKeyPermissions(uid, ticket_view, tmd);
+ if (ret != IPC_SUCCESS)
+ return ret;
+
+ // TODO (for the future): signature checks.
+
+ // Find a signed ticket from the view.
+ const u64 ticket_id = Common::swap64(&ticket_view[offsetof(IOS::ES::TicketView, ticket_id)]);
+ const u64 title_id = Common::swap64(&ticket_view[offsetof(IOS::ES::TicketView, title_id)]);
+ const IOS::ES::TicketReader installed_ticket = DiscIO::FindSignedTicket(title_id);
+ // Unlike the other "get ticket from view" function, this returns a FS error, not ES_NO_TICKET.
+ if (!installed_ticket.IsValid())
+ return FS_ENOENT;
+ const std::vector<u8> ticket_bytes = installed_ticket.GetRawTicket(ticket_id);
+ if (ticket_bytes.empty())
+ return ES_NO_TICKET;
+
+ // Create the handle and return it.
+ std::array<u8, 16> iv{};
+ std::memcpy(iv.data(), &title_id, sizeof(title_id));
+ ret = m_ios.GetIOSC().CreateObject(handle, IOSC::ObjectType::TYPE_SECRET_KEY,
+ IOSC::ObjectSubType::SUBTYPE_AES128, PID_ES);
+ if (ret != IPC_SUCCESS)
+ return ret;
+
+ const u32 owner_uid = uid >= FIRST_PPC_UID ? PID_PPCBOOT : uid;
+ ret = m_ios.GetIOSC().SetOwnership(*handle, 1 << owner_uid, PID_ES);
+ if (ret != IPC_SUCCESS)
+ return ret;
+
+ const u8 index = ticket_bytes[offsetof(IOS::ES::Ticket, common_key_index)];
+ if (index > 1)
+ return ES_INVALID_TICKET;
+
+ auto common_key_handle = index == 0 ? IOSC::HANDLE_COMMON_KEY : IOSC::HANDLE_NEW_COMMON_KEY;
+ return m_ios.GetIOSC().ImportSecretKey(*handle, common_key_handle, iv.data(),
+ &ticket_bytes[offsetof(IOS::ES::Ticket, title_key)],
+ PID_ES);
+}
+
+IPCCommandResult ES::SetUpStreamKey(const Context& context, const IOCtlVRequest& request)
+{
+ if (!request.HasNumberOfValidVectors(2, 1) ||
+ request.in_vectors[0].size != sizeof(IOS::ES::TicketView) ||
+ !IOS::ES::IsValidTMDSize(request.in_vectors[1].size) ||
+ request.io_vectors[0].size != sizeof(u32))
+ {
+ return GetDefaultReply(ES_EINVAL);
+ }
+
+ std::vector<u8> tmd_bytes(request.in_vectors[1].size);
+ Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[1].address, tmd_bytes.size());
+ const IOS::ES::TMDReader tmd{std::move(tmd_bytes)};
+
+ if (!tmd.IsValid())
+ return GetDefaultReply(ES_EINVAL);
+
+ u32 handle;
+ const ReturnCode ret =
+ SetUpStreamKey(context.uid, Memory::GetPointer(request.in_vectors[0].address), tmd, &handle);
+ Memory::Write_U32(handle, request.io_vectors[0].address);
+ return GetDefaultReply(ret);
+}
+
IPCCommandResult ES::DeleteStreamKey(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != sizeof(u32))
@@ -654,6 +767,19 @@ IPCCommandResult ES::DeleteStreamKey(const IOCtlVRequest& request)
const u32 handle = Memory::Read_U32(request.in_vectors[0].address);
return GetDefaultReply(m_ios.GetIOSC().DeleteObject(handle, PID_ES));
}
+
+bool ES::IsActiveTitlePermittedByTicket(const u8* ticket_view) const
+{
+ if (!GetTitleContext().active)
+ return false;
+
+ const u32 title_identifier = static_cast<u32>(GetTitleContext().tmd.GetTitleId());
+ const u32 permitted_title_mask =
+ Common::swap32(ticket_view + offsetof(IOS::ES::TicketView, permitted_title_mask));
+ const u32 permitted_title_id =
+ Common::swap32(ticket_view + offsetof(IOS::ES::TicketView, permitted_title_id));
+ return title_identifier && (title_identifier & ~permitted_title_mask) == permitted_title_id;
+}
} // 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 b56ac57b69..51ad6dffa7 100644
--- a/Source/Core/Core/IOS/ES/ES.h
+++ b/Source/Core/Core/IOS/ES/ES.h
@@ -121,6 +121,9 @@ public:
ReturnCode GetV0TicketFromView(const u8* ticket_view, u8* ticket) const;
ReturnCode GetTicketFromView(const u8* ticket_view, u8* ticket, u32* ticket_size) const;
+ ReturnCode SetUpStreamKey(u32 uid, const u8* ticket_view, const IOS::ES::TMDReader& tmd,
+ u32* handle);
+
private:
enum
{
@@ -183,7 +186,7 @@ private:
IOCTL_ES_DIGETTMDSIZE = 0x39,
IOCTL_ES_DIGETTMD = 0x3A,
IOCTL_ES_DIVERIFY_WITH_VIEW = 0x3B,
- IOCTL_ES_UNKNOWN_3C = 0x3C,
+ IOCTL_ES_SET_UP_STREAM_KEY = 0x3C,
IOCTL_ES_DELETE_STREAM_KEY = 0x3D,
IOCTL_ES_DELETE_CONTENT = 0x3E,
IOCTL_ES_INVALID_3F = 0x3F,
@@ -234,6 +237,7 @@ private:
IPCCommandResult Launch(const IOCtlVRequest& request);
IPCCommandResult LaunchBC(const IOCtlVRequest& request);
IPCCommandResult DIVerify(const IOCtlVRequest& request);
+ IPCCommandResult SetUpStreamKey(const Context& context, const IOCtlVRequest& request);
IPCCommandResult DeleteStreamKey(const IOCtlVRequest& request);
// Title contents
@@ -283,6 +287,10 @@ private:
bool LaunchIOS(u64 ios_title_id);
bool LaunchPPCTitle(u64 title_id, bool skip_reload);
static TitleContext& GetTitleContext();
+ bool IsActiveTitlePermittedByTicket(const u8* ticket_view) const;
+
+ ReturnCode CheckStreamKeyPermissions(u32 uid, const u8* ticket_view,
+ const IOS::ES::TMDReader& tmd) const;
static const DiscIO::NANDContentLoader& AccessContentDevice(u64 title_id);
diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp
index ba99dc77a6..66ebb0dff4 100644
--- a/Source/Core/Core/IOS/ES/Formats.cpp
+++ b/Source/Core/Core/IOS/ES/Formats.cpp
@@ -154,6 +154,11 @@ u64 TMDReader::GetTitleId() const
return Common::swap64(m_bytes.data() + offsetof(TMDHeader, title_id));
}
+u32 TMDReader::GetTitleFlags() const
+{
+ return Common::swap32(m_bytes.data() + offsetof(TMDHeader, title_flags));
+}
+
u16 TMDReader::GetTitleVersion() const
{
return Common::swap16(m_bytes.data() + offsetof(TMDHeader, title_version));
diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h
index 6d53baf1fc..77db4ec3b3 100644
--- a/Source/Core/Core/IOS/ES/Formats.h
+++ b/Source/Core/Core/IOS/ES/Formats.h
@@ -39,6 +39,20 @@ bool IsTitleType(u64 title_id, TitleType title_type);
bool IsDiscTitle(u64 title_id);
bool IsChannel(u64 title_id);
+enum TitleFlags : u32
+{
+ // All official titles have this flag set.
+ TITLE_TYPE_DEFAULT = 0x1,
+ // Unknown.
+ TITLE_TYPE_0x4 = 0x4,
+ // Used for DLC titles.
+ TITLE_TYPE_DATA = 0x8,
+ // Appears to be used for WFS titles.
+ TITLE_TYPE_WFS_MAYBE = 0x20,
+ // Unknown.
+ TITLE_TYPE_CT = 0x40,
+};
+
#pragma pack(push, 4)
struct TMDHeader
{
@@ -51,7 +65,7 @@ struct TMDHeader
u8 signer_crl_version;
u64 ios_id;
u64 title_id;
- u32 title_type;
+ u32 title_flags;
u16 group_id;
u16 zero;
u16 region;
@@ -154,6 +168,7 @@ public:
u64 GetIOSId() const;
DiscIO::Region GetRegion() const;
u64 GetTitleId() const;
+ u32 GetTitleFlags() const;
u16 GetTitleVersion() const;
u16 GetGroupId() const;
diff --git a/Source/Core/Core/IOS/IOSC.cpp b/Source/Core/Core/IOS/IOSC.cpp
index 129d0c485c..438abe3f30 100644
--- a/Source/Core/Core/IOS/IOSC.cpp
+++ b/Source/Core/Core/IOS/IOSC.cpp
@@ -166,7 +166,11 @@ ReturnCode IOSC::SetOwnership(Handle handle, u32 new_owner, u32 pid)
if (!HasOwnership(handle, pid))
return IOSC_EACCES;
- m_key_entries[handle].owner_mask = new_owner;
+ const u32 mask_with_current_pid = 1 << pid;
+ const u32 mask = m_key_entries[handle].owner_mask | mask_with_current_pid;
+ if (mask != mask_with_current_pid)
+ return IOSC_EACCES;
+ m_key_entries[handle].owner_mask = (new_owner & ~7) | mask;
return IPC_SUCCESS;
}