From 5cbbe2dda24e1a099003e2c95641c4e86a914725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 2 Jul 2017 21:22:30 +0200 Subject: [PATCH 1/5] IOSC: Add support for importing decrypted keys directly --- Source/Core/Core/IOS/IOSC.cpp | 20 ++++++++++++++------ Source/Core/Core/IOS/IOSC.h | 2 ++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/IOS/IOSC.cpp b/Source/Core/Core/IOS/IOSC.cpp index 16d1734a29..a83a84f50f 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, 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); From 5b09657a1fee073d6290105fefcf538b10b05fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 2 Jul 2017 21:23:00 +0200 Subject: [PATCH 2/5] ESFormats: Add entry for unknown title flag --- Source/Core/Core/IOS/ES/Formats.h | 2 ++ 1 file changed, 2 insertions(+) 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. From 05016e8dcac8b8b7177b72bffe34c96dfd4a5df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 2 Jul 2017 21:51:01 +0200 Subject: [PATCH 3/5] EcWii: Add function to get the backup key --- Source/Core/Core/ec_wii.cpp | 5 +++++ Source/Core/Core/ec_wii.h | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) 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 From e608d79f424dae9371ebe0df27fbb0c9e53b9afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 2 Jul 2017 21:51:53 +0200 Subject: [PATCH 4/5] IOSC: Load the backup/PRNG key --- Source/Core/Core/IOS/IOSC.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/IOS/IOSC.cpp b/Source/Core/Core/IOS/IOSC.cpp index a83a84f50f..edb6fdf734 100644 --- a/Source/Core/Core/IOS/IOSC.cpp +++ b/Source/Core/Core/IOS/IOSC.cpp @@ -430,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, From fbcc6bbd5719e37b8e1dc1a68e3b3bdd5b1cf7a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 2 Jul 2017 21:32:03 +0200 Subject: [PATCH 5/5] IOS/ES: Use the correct key for imports/exports Imports/exports don't always use the title key. Exporting a title and importing it back uses the PRNG key (aka backup key handle or key #5), not the title key (at all). To make things even more fun, 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. (Thanks to tueidj for drawing my attention to this. I missed this edge case during the initial implementation.) This commit implements these checks so we are using the correct key in all of these cases. We now also use IOSC for decryption/encryption since built-in key handles are used. And we now reject any invalid common key index, just like ES. --- Source/Core/Core/IOS/ES/ES.h | 3 +- Source/Core/Core/IOS/ES/TitleManagement.cpp | 118 +++++++++++++++----- 2 files changed, 89 insertions(+), 32 deletions(-) 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; }