IOS/ES: Implement ES_SetUpStreamKey

This ioctlv is used to get an IOSC decrypt handle for a title.
It is known to be used internally by the WFS modules, but it can also
be used from the PPC under some conditions.

Brings us down to 2 essentially unimplementable ioctlvs (syscalls which
seem to return kernel thread priorities...), and 1 known but
unimplemented ioctlv (VerifySign).
This commit is contained in:
Léo Lam 2017-06-08 00:31:44 +02:00
parent e240e260d9
commit 7b4404c7d5
4 changed files with 157 additions and 3 deletions

View File

@ -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

View File

@ -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);

View File

@ -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));

View File

@ -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;