diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 929638ecb5..6352eec917 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -13,6 +13,7 @@ #include "Core/IOS/Device.h" #include "Core/IOS/ES/Formats.h" #include "Core/IOS/IOS.h" +#include "Core/IOS/IOSC.h" class PointerWrap; @@ -61,7 +62,7 @@ public: bool valid = false; IOS::ES::TMDReader tmd; - std::array key{}; + IOSC::Handle key_handle = 0; struct ContentContext { bool valid = false; diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 7066eacfb4..1ff653c936 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -13,12 +13,12 @@ #include #include "Common/Align.h" -#include "Common/Crypto/AES.h" #include "Common/File.h" #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/NandPaths.h" #include "Common/StringUtil.h" +#include "Core/CommonTitles.h" #include "Core/HW/Memmap.h" #include "Core/IOS/ES/Formats.h" #include "Core/ec_wii.h" @@ -48,7 +48,7 @@ static ReturnCode WriteTicket(const IOS::ES::TicketReader& ticket) void ES::TitleImportExportContext::DoState(PointerWrap& p) { p.Do(valid); - p.Do(key); + p.Do(key_handle); tmd.DoState(p); p.Do(content.valid); p.Do(content.id); @@ -105,11 +105,43 @@ IPCCommandResult ES::ImportTicket(const IOCtlVRequest& request) return GetDefaultReply(ImportTicket(bytes, cert_chain)); } +constexpr std::array NULL_KEY{}; + +// Used for exporting titles and importing them back (ImportTmd and ExportTitleInit). +static ReturnCode InitBackupKey(const IOS::ES::TMDReader& tmd, IOSC& iosc, IOSC::Handle* key) +{ + // Some versions of IOS have a bug that causes it to use a zeroed key instead of the PRNG key. + // When Nintendo decided to fix it, they added checks to keep using the zeroed key only in + // affected titles to avoid making existing exports useless. + + // Ignore the region byte. + const u64 title_id = tmd.GetTitleId() | 0xff; + const u32 title_flags = tmd.GetTitleFlags(); + const u32 affected_type = IOS::ES::TITLE_TYPE_0x10 | IOS::ES::TITLE_TYPE_DATA; + if (title_id == Titles::SYSTEM_MENU || (title_flags & affected_type) != affected_type || + !(title_id == 0x00010005735841ff || title_id - 0x00010005735a41ff <= 0x700)) + { + *key = IOSC::HANDLE_PRNG_KEY; + return IPC_SUCCESS; + } + + // Otherwise, use a null key. + ReturnCode ret = iosc.CreateObject(key, IOSC::TYPE_SECRET_KEY, IOSC::SUBTYPE_AES128, PID_ES); + return ret == IPC_SUCCESS ? iosc.ImportSecretKey(*key, NULL_KEY.data(), PID_ES) : ret; +} + +static void ResetTitleImportContext(ES::Context* context, IOSC& iosc) +{ + if (context->title_import_export.key_handle) + iosc.DeleteObject(context->title_import_export.key_handle, PID_ES); + context->title_import_export = {}; +} + ReturnCode ES::ImportTmd(Context& context, const std::vector& 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_export = {}; + ResetTitleImportContext(&context, m_ios.GetIOSC()); context.title_import_export.tmd.SetBytes(tmd_bytes); if (!context.title_import_export.tmd.IsValid()) return ES_EINVAL; @@ -127,11 +159,10 @@ ReturnCode ES::ImportTmd(Context& context, const std::vector& tmd_bytes) if (!InitImport(context.title_import_export.tmd.GetTitleId())) return ES_EIO; - // FIXME: ImportTmd does not use the ticket or the title key. - const auto ticket = DiscIO::FindSignedTicket(context.title_import_export.tmd.GetTitleId()); - if (!ticket.IsValid()) - return ES_NO_TICKET; - context.title_import_export.key = ticket.GetTitleKey(m_ios.GetIOSC()); + ret = InitBackupKey(GetTitleContext().tmd, m_ios.GetIOSC(), + &context.title_import_export.key_handle); + if (ret != IPC_SUCCESS) + return ret; context.title_import_export.valid = true; return IPC_SUCCESS; @@ -150,11 +181,29 @@ IPCCommandResult ES::ImportTmd(Context& context, const IOCtlVRequest& request) return GetDefaultReply(ImportTmd(context, tmd)); } +static ReturnCode InitTitleImportKey(const std::vector& ticket_bytes, IOSC& iosc, + IOSC::Handle* handle) +{ + ReturnCode ret = iosc.CreateObject(handle, IOSC::TYPE_SECRET_KEY, IOSC::SUBTYPE_AES128, PID_ES); + if (ret != IPC_SUCCESS) + return ret; + + std::array iv{}; + std::copy_n(&ticket_bytes[offsetof(IOS::ES::Ticket, title_id)], sizeof(u64), iv.begin()); + const u8 index = ticket_bytes[offsetof(IOS::ES::Ticket, common_key_index)]; + if (index > 1) + return ES_INVALID_TICKET; + + return iosc.ImportSecretKey( + *handle, index == 0 ? IOSC::HANDLE_COMMON_KEY : IOSC::HANDLE_NEW_COMMON_KEY, iv.data(), + &ticket_bytes[offsetof(IOS::ES::Ticket, title_key)], PID_ES); +} + ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_bytes, const std::vector& cert_chain) { INFO_LOG(IOS_ES, "ImportTitleInit"); - context.title_import_export = {}; + ResetTitleImportContext(&context, m_ios.GetIOSC()); context.title_import_export.tmd.SetBytes(tmd_bytes); if (!context.title_import_export.tmd.IsValid()) { @@ -174,8 +223,6 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_byte if (!ticket.IsValid()) return ES_NO_TICKET; - context.title_import_export.key = ticket.GetTitleKey(m_ios.GetIOSC()); - std::vector cert_store; ret = ReadCertStore(&cert_store); if (ret != IPC_SUCCESS) @@ -186,6 +233,11 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_byte if (ret != IPC_SUCCESS) return ret; + ret = InitTitleImportKey(ticket.GetBytes(), m_ios.GetIOSC(), + &context.title_import_export.key_handle); + if (ret != IPC_SUCCESS) + return ret; + if (!InitImport(context.title_import_export.tmd.GetTitleId())) return ES_EIO; @@ -300,10 +352,14 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd) if (!context.title_import_export.valid || !context.title_import_export.content.valid) return ES_EINVAL; - std::vector decrypted_data = Common::AES::Decrypt( - context.title_import_export.key.data(), context.title_import_export.content.iv.data(), + std::vector decrypted_data(context.title_import_export.content.buffer.size()); + const ReturnCode decrypt_ret = m_ios.GetIOSC().Decrypt( + context.title_import_export.key_handle, context.title_import_export.content.iv.data(), context.title_import_export.content.buffer.data(), - context.title_import_export.content.buffer.size()); + context.title_import_export.content.buffer.size(), decrypted_data.data(), PID_ES); + if (decrypt_ret != IPC_SUCCESS) + return decrypt_ret; + IOS::ES::Content content_info; context.title_import_export.tmd.FindContentById(context.title_import_export.content.id, &content_info); @@ -391,7 +447,7 @@ ReturnCode ES::ImportTitleDone(Context& context) return ES_EIO; INFO_LOG(IOS_ES, "ImportTitleDone: title %016" PRIx64, title_id); - context.title_import_export = {}; + ResetTitleImportContext(&context, m_ios.GetIOSC()); return IPC_SUCCESS; } @@ -417,7 +473,7 @@ ReturnCode ES::ImportTitleCancel(Context& context) INFO_LOG(IOS_ES, "ImportTitleCancel: title %016" PRIx64, title_id); } - context.title_import_export = {}; + ResetTitleImportContext(&context, m_ios.GetIOSC()); return IPC_SUCCESS; } @@ -579,17 +635,13 @@ ReturnCode ES::ExportTitleInit(Context& context, u64 title_id, u8* tmd_bytes, u3 if (!tmd.IsValid()) return FS_ENOENT; - context.title_import_export = {}; + ResetTitleImportContext(&context, m_ios.GetIOSC()); context.title_import_export.tmd = tmd; - const auto ticket = DiscIO::FindSignedTicket(context.title_import_export.tmd.GetTitleId()); - if (!ticket.IsValid()) - return ES_NO_TICKET; - if (ticket.GetTitleId() != context.title_import_export.tmd.GetTitleId()) - return ES_EINVAL; - - // FIXME: this is wrong. The title key is *not* used here. Key #5 or a null key is. - context.title_import_export.key = ticket.GetTitleKey(m_ios.GetIOSC()); + const ReturnCode ret = InitBackupKey(GetTitleContext().tmd, m_ios.GetIOSC(), + &context.title_import_export.key_handle); + if (ret != IPC_SUCCESS) + return ret; const std::vector& raw_tmd = context.title_import_export.tmd.GetBytes(); if (tmd_size != raw_tmd.size()) @@ -633,7 +685,7 @@ ReturnCode ES::ExportContentBegin(Context& context, u64 title_id, u32 content_id const s32 ret = OpenContent(context.title_import_export.tmd, content_info.index, 0); if (ret < 0) { - context.title_import_export = {}; + ResetTitleImportContext(&context, m_ios.GetIOSC()); return static_cast(ret); } @@ -671,7 +723,7 @@ ReturnCode ES::ExportContentData(Context& context, u32 content_fd, u8* data, u32 if (read_size < 0) { CloseContent(content_fd, 0); - context.title_import_export = {}; + ResetTitleImportContext(&context, m_ios.GetIOSC()); return ES_SHORT_READ; } @@ -679,9 +731,13 @@ ReturnCode ES::ExportContentData(Context& context, u32 content_fd, u8* data, u32 // let's just follow IOS here. buffer.resize(Common::AlignUp(buffer.size(), 32)); - const std::vector output = Common::AES::Encrypt(context.title_import_export.key.data(), - context.title_import_export.content.iv.data(), - buffer.data(), buffer.size()); + std::vector output(buffer.size()); + const ReturnCode decrypt_ret = m_ios.GetIOSC().Encrypt( + context.title_import_export.key_handle, context.title_import_export.content.iv.data(), + buffer.data(), buffer.size(), output.data(), PID_ES); + if (decrypt_ret != IPC_SUCCESS) + return decrypt_ret; + std::copy(output.cbegin(), output.cend(), data); return IPC_SUCCESS; } @@ -719,7 +775,7 @@ IPCCommandResult ES::ExportContentEnd(Context& context, const IOCtlVRequest& req ReturnCode ES::ExportTitleDone(Context& context) { - context.title_import_export = {}; + ResetTitleImportContext(&context, m_ios.GetIOSC()); return IPC_SUCCESS; }