Merge pull request #5739 from leoetlino/correct-key
IOS/ES: Use the correct import/export key (fix DLC)
This commit is contained in:
commit
81abecbf46
|
@ -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<u8, 16> key{};
|
||||
IOSC::Handle key_handle = 0;
|
||||
struct ContentContext
|
||||
{
|
||||
bool valid = false;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
#include <mbedtls/sha1.h>
|
||||
|
||||
#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<u8, 16> 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<u8>& 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<u8>& 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<u8>& 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<u8, 16> 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<u8>& tmd_bytes,
|
||||
const std::vector<u8>& 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<u8>& tmd_byte
|
|||
if (!ticket.IsValid())
|
||||
return ES_NO_TICKET;
|
||||
|
||||
context.title_import_export.key = ticket.GetTitleKey(m_ios.GetIOSC());
|
||||
|
||||
std::vector<u8> cert_store;
|
||||
ret = ReadCertStore(&cert_store);
|
||||
if (ret != IPC_SUCCESS)
|
||||
|
@ -186,6 +233,11 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector<u8>& 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<u8> decrypted_data = Common::AES::Decrypt(
|
||||
context.title_import_export.key.data(), context.title_import_export.content.iv.data(),
|
||||
std::vector<u8> 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<u8>& 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<ReturnCode>(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<u8> output = Common::AES::Encrypt(context.title_import_export.key.data(),
|
||||
context.title_import_export.content.iv.data(),
|
||||
buffer.data(), buffer.size());
|
||||
std::vector<u8> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<u8, AES128_KEY_SIZE> 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<u8>(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<u8>(16), 3};
|
||||
m_key_entries[HANDLE_PRNG_KEY] = {
|
||||
TYPE_SECRET_KEY, SUBTYPE_AES128,
|
||||
std::vector<u8>(ec.GetBackupKey(), ec.GetBackupKey() + AES128_KEY_SIZE), 3};
|
||||
|
||||
m_key_entries[HANDLE_SD_KEY] = {TYPE_SECRET_KEY,
|
||||
SUBTYPE_AES128,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue