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:
parent
e240e260d9
commit
7b4404c7d5
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue