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/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index 5f01e7eef6..f1d3a0c83a 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -49,6 +49,8 @@ enum TitleFlags : u32 TITLE_TYPE_0x4 = 0x4, // Used for DLC titles. TITLE_TYPE_DATA = 0x8, + // Unknown. + TITLE_TYPE_0x10 = 0x10, // Appears to be used for WFS titles. TITLE_TYPE_WFS_MAYBE = 0x20, // Unknown. 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; } diff --git a/Source/Core/Core/IOS/IOSC.cpp b/Source/Core/Core/IOS/IOSC.cpp index 16d1734a29..edb6fdf734 100644 --- a/Source/Core/Core/IOS/IOSC.cpp +++ b/Source/Core/Core/IOS/IOSC.cpp @@ -85,11 +85,19 @@ constexpr size_t AES128_KEY_SIZE = 0x10; ReturnCode IOSC::ImportSecretKey(Handle dest_handle, Handle decrypt_handle, u8* iv, const u8* encrypted_key, u32 pid) { - if (!HasOwnership(dest_handle, pid) || !HasOwnership(decrypt_handle, pid) || - IsDefaultHandle(dest_handle)) - { + std::array decrypted_key; + const ReturnCode ret = + Decrypt(decrypt_handle, iv, encrypted_key, AES128_KEY_SIZE, decrypted_key.data(), pid); + if (ret != IPC_SUCCESS) + return ret; + + return ImportSecretKey(dest_handle, decrypted_key.data(), pid); +} + +ReturnCode IOSC::ImportSecretKey(Handle dest_handle, const u8* decrypted_key, u32 pid) +{ + if (!HasOwnership(dest_handle, pid) || IsDefaultHandle(dest_handle)) return IOSC_EACCES; - } KeyEntry* dest_entry = FindEntry(dest_handle); if (!dest_entry) @@ -99,8 +107,8 @@ ReturnCode IOSC::ImportSecretKey(Handle dest_handle, Handle decrypt_handle, u8* if (dest_entry->type != TYPE_SECRET_KEY || dest_entry->subtype != SUBTYPE_AES128) return IOSC_INVALID_OBJTYPE; - dest_entry->data.resize(AES128_KEY_SIZE); - return Decrypt(decrypt_handle, iv, encrypted_key, AES128_KEY_SIZE, dest_entry->data.data(), pid); + dest_entry->data = std::vector(decrypted_key, decrypted_key + AES128_KEY_SIZE); + return IPC_SUCCESS; } ReturnCode IOSC::ImportPublicKey(Handle dest_handle, const u8* public_key, @@ -422,8 +430,9 @@ void IOSC::LoadDefaultEntries(ConsoleType console_type) break; } - // Unimplemented. - m_key_entries[HANDLE_PRNG_KEY] = {TYPE_SECRET_KEY, SUBTYPE_AES128, std::vector(16), 3}; + m_key_entries[HANDLE_PRNG_KEY] = { + TYPE_SECRET_KEY, SUBTYPE_AES128, + std::vector(ec.GetBackupKey(), ec.GetBackupKey() + AES128_KEY_SIZE), 3}; m_key_entries[HANDLE_SD_KEY] = {TYPE_SECRET_KEY, SUBTYPE_AES128, diff --git a/Source/Core/Core/IOS/IOSC.h b/Source/Core/Core/IOS/IOSC.h index 653b7a3a01..33ddc3ded2 100644 --- a/Source/Core/Core/IOS/IOSC.h +++ b/Source/Core/Core/IOS/IOSC.h @@ -172,6 +172,8 @@ public: // Import a secret, encrypted key into dest_handle, which will be decrypted using decrypt_handle. ReturnCode ImportSecretKey(Handle dest_handle, Handle decrypt_handle, u8* iv, const u8* encrypted_key, u32 pid); + // Import a secret key that is already decrypted. + ReturnCode ImportSecretKey(Handle dest_handle, const u8* decrypted_key, u32 pid); // Import a public key. public_key_exponent must be passed for RSA keys. ReturnCode ImportPublicKey(Handle dest_handle, const u8* public_key, const u8* public_key_exponent, u32 pid); diff --git a/Source/Core/Core/ec_wii.cpp b/Source/Core/Core/ec_wii.cpp index 28ffd0dabd..6411852403 100644 --- a/Source/Core/Core/ec_wii.cpp +++ b/Source/Core/Core/ec_wii.cpp @@ -181,6 +181,11 @@ const u8* EcWii::GetNGSig() const return BootMiiKeysBin.ng_sig; } +const u8* EcWii::GetBackupKey() const +{ + return BootMiiKeysBin.backup_key; +} + void EcWii::InitDefaults() { memset(&BootMiiKeysBin, 0, sizeof(BootMiiKeysBin)); diff --git a/Source/Core/Core/ec_wii.h b/Source/Core/Core/ec_wii.h index eb4395a9a7..2a9ec003a8 100644 --- a/Source/Core/Core/ec_wii.h +++ b/Source/Core/Core/ec_wii.h @@ -42,6 +42,7 @@ public: u32 GetNGKeyID() const; const u8* GetNGPriv() const; const u8* GetNGSig() const; + const u8* GetBackupKey() const; private: void InitDefaults(); @@ -82,7 +83,7 @@ private: }; }; u8 nand_key[0x10]; // 0x158 - u8 rng_key[0x10]; // 0x168 + u8 backup_key[0x10]; // 0x168 u32 unk1; // 0x178 u32 unk2; // 0x17C u8 eeprom_pad[0x80]; // 0x180