Merge pull request #5354 from leoetlino/iosc
IOS: Implement IOSC-like library (+ bug fixes)
This commit is contained in:
commit
5088fac54b
|
@ -10,15 +10,30 @@ namespace Common
|
|||
{
|
||||
namespace AES
|
||||
{
|
||||
std::vector<u8> Decrypt(const u8* key, u8* iv, const u8* src, size_t size)
|
||||
std::vector<u8> DecryptEncrypt(const u8* key, u8* iv, const u8* src, size_t size, Mode mode)
|
||||
{
|
||||
mbedtls_aes_context aes_ctx;
|
||||
std::vector<u8> buffer(size);
|
||||
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, key, 128);
|
||||
mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, size, iv, src, buffer.data());
|
||||
if (mode == Mode::Encrypt)
|
||||
mbedtls_aes_setkey_enc(&aes_ctx, key, 128);
|
||||
else
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, key, 128);
|
||||
|
||||
mbedtls_aes_crypt_cbc(&aes_ctx, mode == Mode::Encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT,
|
||||
size, iv, src, buffer.data());
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::vector<u8> Decrypt(const u8* key, u8* iv, const u8* src, size_t size)
|
||||
{
|
||||
return DecryptEncrypt(key, iv, src, size, Mode::Decrypt);
|
||||
}
|
||||
|
||||
std::vector<u8> Encrypt(const u8* key, u8* iv, const u8* src, size_t size)
|
||||
{
|
||||
return DecryptEncrypt(key, iv, src, size, Mode::Encrypt);
|
||||
}
|
||||
} // namespace AES
|
||||
} // namespace Common
|
||||
|
|
|
@ -13,6 +13,15 @@ namespace Common
|
|||
{
|
||||
namespace AES
|
||||
{
|
||||
enum class Mode
|
||||
{
|
||||
Decrypt,
|
||||
Encrypt,
|
||||
};
|
||||
std::vector<u8> DecryptEncrypt(const u8* key, u8* iv, const u8* src, size_t size, Mode mode);
|
||||
|
||||
// Convenience functions
|
||||
std::vector<u8> Decrypt(const u8* key, u8* iv, const u8* src, size_t size);
|
||||
std::vector<u8> Encrypt(const u8* key, u8* iv, const u8* src, size_t size);
|
||||
} // namespace AES
|
||||
} // namespace Common
|
||||
|
|
|
@ -148,6 +148,7 @@ set(SRCS
|
|||
IOS/Device.cpp
|
||||
IOS/DeviceStub.cpp
|
||||
IOS/IOS.cpp
|
||||
IOS/IOSC.cpp
|
||||
IOS/MemoryValues.cpp
|
||||
IOS/MIOS.cpp
|
||||
IOS/DI/DI.cpp
|
||||
|
|
|
@ -175,6 +175,7 @@
|
|||
<ClCompile Include="IOS\Device.cpp" />
|
||||
<ClCompile Include="IOS\DeviceStub.cpp" />
|
||||
<ClCompile Include="IOS\IOS.cpp" />
|
||||
<ClCompile Include="IOS\IOSC.cpp" />
|
||||
<ClCompile Include="IOS\MemoryValues.cpp" />
|
||||
<ClCompile Include="IOS\MIOS.cpp" />
|
||||
<ClCompile Include="IOS\DI\DI.cpp" />
|
||||
|
@ -432,6 +433,7 @@
|
|||
<ClInclude Include="IOS\Device.h" />
|
||||
<ClInclude Include="IOS\DeviceStub.h" />
|
||||
<ClInclude Include="IOS\IOS.h" />
|
||||
<ClInclude Include="IOS\IOSC.h" />
|
||||
<ClInclude Include="IOS\MemoryValues.h" />
|
||||
<ClInclude Include="IOS\MIOS.h" />
|
||||
<ClInclude Include="IOS\DI\DI.h" />
|
||||
|
|
|
@ -793,6 +793,9 @@
|
|||
<ClCompile Include="IOS\IOS.cpp">
|
||||
<Filter>IOS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="IOS\IOSC.cpp">
|
||||
<Filter>IOS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="IOS\MemoryValues.cpp">
|
||||
<Filter>IOS</Filter>
|
||||
</ClCompile>
|
||||
|
@ -1507,6 +1510,9 @@
|
|||
<ClInclude Include="IOS\IOS.h">
|
||||
<Filter>IOS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IOS\IOSC.h">
|
||||
<Filter>IOS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IOS\MemoryValues.h">
|
||||
<Filter>IOS</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
@ -23,6 +23,7 @@ enum ReturnCode : s32
|
|||
IPC_EACCES = -1, // Permission denied
|
||||
IPC_EEXIST = -2, // File exists
|
||||
IPC_EINVAL = -4, // Invalid argument or fd
|
||||
IPC_EMAX = -5, // Too many file descriptors open
|
||||
IPC_ENOENT = -6, // File not found
|
||||
IPC_EQUEUEFULL = -8, // Queue full
|
||||
IPC_EIO = -12, // ECC error
|
||||
|
@ -83,6 +84,7 @@ struct Request
|
|||
|
||||
enum OpenMode : s32
|
||||
{
|
||||
IOS_OPEN_NONE = 0,
|
||||
IOS_OPEN_READ = 1,
|
||||
IOS_OPEN_WRITE = 2,
|
||||
IOS_OPEN_RW = (IOS_OPEN_READ | IOS_OPEN_WRITE)
|
||||
|
@ -106,14 +108,15 @@ struct ReadWriteRequest final : Request
|
|||
explicit ReadWriteRequest(u32 address);
|
||||
};
|
||||
|
||||
enum SeekMode : s32
|
||||
{
|
||||
IOS_SEEK_SET = 0,
|
||||
IOS_SEEK_CUR = 1,
|
||||
IOS_SEEK_END = 2,
|
||||
};
|
||||
|
||||
struct SeekRequest final : Request
|
||||
{
|
||||
enum SeekMode
|
||||
{
|
||||
IOS_SEEK_SET = 0,
|
||||
IOS_SEEK_CUR = 1,
|
||||
IOS_SEEK_END = 2,
|
||||
};
|
||||
u32 offset = 0;
|
||||
SeekMode mode = IOS_SEEK_SET;
|
||||
explicit SeekRequest(u32 address);
|
||||
|
|
|
@ -49,9 +49,6 @@ public:
|
|||
static void LoadWAD(const std::string& _rContentFile);
|
||||
bool LaunchTitle(u64 title_id, bool skip_reload = false);
|
||||
|
||||
// Internal implementation of the ES_DECRYPT ioctlv.
|
||||
static void DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output);
|
||||
|
||||
void DoState(PointerWrap& p) override;
|
||||
|
||||
ReturnCode Open(const OpenRequest& request) override;
|
||||
|
|
|
@ -16,12 +16,12 @@
|
|||
#include "Common/Assert.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Crypto/AES.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/ec_wii.h"
|
||||
#include "Core/IOS/Device.h"
|
||||
#include "Core/IOS/IOSC.h"
|
||||
|
||||
namespace IOS
|
||||
{
|
||||
|
@ -313,15 +313,18 @@ u64 TicketReader::GetTitleId() const
|
|||
|
||||
std::vector<u8> TicketReader::GetTitleKey() const
|
||||
{
|
||||
const u8 common_key[16] = {0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4,
|
||||
0x48, 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7};
|
||||
u8 iv[16] = {};
|
||||
std::copy_n(&m_bytes[GetOffset() + offsetof(Ticket, title_id)], sizeof(Ticket::title_id), iv);
|
||||
return Common::AES::Decrypt(common_key, iv, &m_bytes[GetOffset() + offsetof(Ticket, title_key)],
|
||||
16);
|
||||
}
|
||||
auto common_key_handle = m_bytes.at(GetOffset() + offsetof(Ticket, common_key_index)) == 0 ?
|
||||
HLE::IOSC::HANDLE_COMMON_KEY :
|
||||
HLE::IOSC::HANDLE_NEW_COMMON_KEY;
|
||||
|
||||
constexpr s32 IOSC_OK = 0;
|
||||
std::vector<u8> key(16);
|
||||
HLE::IOSC iosc;
|
||||
iosc.Decrypt(common_key_handle, iv, &m_bytes[GetOffset() + offsetof(Ticket, title_key)], 16,
|
||||
key.data(), HLE::PID_ES);
|
||||
return key;
|
||||
}
|
||||
|
||||
s32 TicketReader::Unpersonalise()
|
||||
{
|
||||
|
@ -329,24 +332,38 @@ s32 TicketReader::Unpersonalise()
|
|||
|
||||
// IOS uses IOSC to compute an AES key from the peer public key and the device's private ECC key,
|
||||
// which is used the decrypt the title key. The IV is the ticket ID (8 bytes), zero extended.
|
||||
using namespace HLE;
|
||||
IOSC iosc;
|
||||
IOSC::Handle public_handle;
|
||||
s32 ret = iosc.CreateObject(&public_handle, IOSC::TYPE_PUBLIC_KEY, IOSC::SUBTYPE_ECC233, PID_ES);
|
||||
if (ret != IPC_SUCCESS)
|
||||
return ret;
|
||||
|
||||
const auto public_key_iter = ticket_begin + offsetof(Ticket, server_public_key);
|
||||
EcWii::ECCKey public_key;
|
||||
std::copy_n(public_key_iter, sizeof(Ticket::server_public_key), public_key.begin());
|
||||
ret = iosc.ImportPublicKey(public_handle, &*public_key_iter, PID_ES);
|
||||
if (ret != IPC_SUCCESS)
|
||||
return ret;
|
||||
|
||||
const EcWii& ec = EcWii::GetInstance();
|
||||
const std::array<u8, 16> shared_secret = ec.GetSharedSecret(public_key);
|
||||
IOSC::Handle key_handle;
|
||||
ret = iosc.CreateObject(&key_handle, IOSC::TYPE_SECRET_KEY, IOSC::SUBTYPE_AES128, PID_ES);
|
||||
if (ret != IPC_SUCCESS)
|
||||
return ret;
|
||||
|
||||
ret = iosc.ComputeSharedKey(key_handle, IOSC::HANDLE_CONSOLE_KEY, public_handle, PID_ES);
|
||||
if (ret != IPC_SUCCESS)
|
||||
return ret;
|
||||
|
||||
std::array<u8, 16> iv{};
|
||||
std::copy_n(ticket_begin + offsetof(Ticket, ticket_id), sizeof(Ticket::ticket_id), iv.begin());
|
||||
|
||||
const std::vector<u8> key =
|
||||
Common::AES::Decrypt(shared_secret.data(), iv.data(),
|
||||
&*ticket_begin + offsetof(Ticket, title_key), sizeof(Ticket::title_key));
|
||||
|
||||
std::array<u8, 16> key{};
|
||||
ret = iosc.Decrypt(key_handle, iv.data(), &*ticket_begin + offsetof(Ticket, title_key),
|
||||
sizeof(Ticket::title_key), key.data(), PID_ES);
|
||||
// Finally, IOS copies the decrypted title key back to the ticket buffer.
|
||||
std::copy(key.cbegin(), key.cend(), ticket_begin + offsetof(Ticket, title_key));
|
||||
return IOSC_OK;
|
||||
if (ret == IPC_SUCCESS)
|
||||
std::copy(key.cbegin(), key.cend(), ticket_begin + offsetof(Ticket, title_key));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct SharedContentMap::Entry
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/aes.h>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
|
@ -21,37 +19,6 @@ namespace HLE
|
|||
{
|
||||
namespace Device
|
||||
{
|
||||
constexpr u8 s_key_sd[0x10] = {0xab, 0x01, 0xb9, 0xd8, 0xe1, 0x62, 0x2b, 0x08,
|
||||
0xaf, 0xba, 0xd8, 0x4d, 0xbf, 0xc2, 0xa5, 0x5d};
|
||||
constexpr u8 s_key_ecc[0x1e] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
|
||||
constexpr u8 s_key_empty[0x10] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
// default key table
|
||||
constexpr const u8* s_key_table[11] = {
|
||||
s_key_ecc, // ECC Private Key
|
||||
s_key_empty, // Console ID
|
||||
s_key_empty, // NAND AES Key
|
||||
s_key_empty, // NAND HMAC
|
||||
s_key_empty, // Common Key
|
||||
s_key_empty, // PRNG seed
|
||||
s_key_sd, // SD Key
|
||||
s_key_empty, // Unknown
|
||||
s_key_empty, // Unknown
|
||||
s_key_empty, // Unknown
|
||||
s_key_empty, // Unknown
|
||||
};
|
||||
|
||||
void ES::DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output)
|
||||
{
|
||||
mbedtls_aes_context AES_ctx;
|
||||
mbedtls_aes_setkey_dec(&AES_ctx, s_key_table[key_index], 128);
|
||||
memcpy(new_iv, iv, 16);
|
||||
mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_DECRYPT, size, new_iv, input, output);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::GetConsoleID(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(0, 1))
|
||||
|
@ -69,20 +36,15 @@ IPCCommandResult ES::Encrypt(u32 uid, const IOCtlVRequest& request)
|
|||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
u32 keyIndex = Memory::Read_U32(request.in_vectors[0].address);
|
||||
u8* IV = Memory::GetPointer(request.in_vectors[1].address);
|
||||
u8* source = Memory::GetPointer(request.in_vectors[2].address);
|
||||
u32 size = request.in_vectors[2].size;
|
||||
u8* newIV = Memory::GetPointer(request.io_vectors[0].address);
|
||||
u8* iv = Memory::GetPointer(request.io_vectors[0].address);
|
||||
u8* destination = Memory::GetPointer(request.io_vectors[1].address);
|
||||
|
||||
mbedtls_aes_context AES_ctx;
|
||||
mbedtls_aes_setkey_enc(&AES_ctx, s_key_table[keyIndex], 128);
|
||||
memcpy(newIV, IV, 16);
|
||||
mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_ENCRYPT, size, newIV, source, destination);
|
||||
// TODO: Check whether the active title is allowed to encrypt.
|
||||
|
||||
_dbg_assert_msg_(IOS_ES, keyIndex == 6,
|
||||
"IOCTL_ES_ENCRYPT: Key type is not SD, data will be crap");
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
const ReturnCode ret = m_ios.GetIOSC().Encrypt(keyIndex, iv, source, size, destination, PID_ES);
|
||||
return GetDefaultReply(ret);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::Decrypt(u32 uid, const IOCtlVRequest& request)
|
||||
|
@ -91,17 +53,15 @@ IPCCommandResult ES::Decrypt(u32 uid, const IOCtlVRequest& request)
|
|||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
u32 keyIndex = Memory::Read_U32(request.in_vectors[0].address);
|
||||
u8* IV = Memory::GetPointer(request.in_vectors[1].address);
|
||||
u8* source = Memory::GetPointer(request.in_vectors[2].address);
|
||||
u32 size = request.in_vectors[2].size;
|
||||
u8* newIV = Memory::GetPointer(request.io_vectors[0].address);
|
||||
u8* iv = Memory::GetPointer(request.io_vectors[0].address);
|
||||
u8* destination = Memory::GetPointer(request.io_vectors[1].address);
|
||||
|
||||
DecryptContent(keyIndex, IV, source, size, newIV, destination);
|
||||
// TODO: Check whether the active title is allowed to decrypt.
|
||||
|
||||
_dbg_assert_msg_(IOS_ES, keyIndex == 6,
|
||||
"IOCTL_ES_DECRYPT: Key type is not SD, data will be crap");
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
const ReturnCode ret = m_ios.GetIOSC().Decrypt(keyIndex, iv, source, size, destination, PID_ES);
|
||||
return GetDefaultReply(ret);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::CheckKoreaRegion(const IOCtlVRequest& request)
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/Align.h"
|
||||
#include "Common/Crypto/AES.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/NandPaths.h"
|
||||
|
@ -207,9 +207,6 @@ IPCCommandResult ES::AddContentFinish(Context& context, const IOCtlVRequest& req
|
|||
return GetDefaultReply(ES_NO_TICKET);
|
||||
}
|
||||
|
||||
mbedtls_aes_context aes_ctx;
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, ticket.GetTitleKey().data(), 128);
|
||||
|
||||
// The IV for title content decryption is the lower two bytes of the
|
||||
// content index, zero extended.
|
||||
IOS::ES::Content content_info;
|
||||
|
@ -220,9 +217,9 @@ IPCCommandResult ES::AddContentFinish(Context& context, const IOCtlVRequest& req
|
|||
u8 iv[16] = {0};
|
||||
iv[0] = (content_info.index >> 8) & 0xFF;
|
||||
iv[1] = content_info.index & 0xFF;
|
||||
std::vector<u8> decrypted_data(context.title_import.content_buffer.size());
|
||||
mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, context.title_import.content_buffer.size(),
|
||||
iv, context.title_import.content_buffer.data(), decrypted_data.data());
|
||||
std::vector<u8> decrypted_data = Common::AES::Decrypt(ticket.GetTitleKey().data(), iv,
|
||||
context.title_import.content_buffer.data(),
|
||||
context.title_import.content_buffer.size());
|
||||
if (!CheckIfContentHashMatches(decrypted_data, content_info))
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "AddContentFinish: Hash for content %08x doesn't match", content_info.id);
|
||||
|
@ -480,18 +477,10 @@ IPCCommandResult ES::ExportContentData(Context& context, const IOCtlVRequest& re
|
|||
// IOS aligns the buffer to 32 bytes. Since we also need to align it to 16 bytes,
|
||||
// let's just follow IOS here.
|
||||
buffer.resize(Common::AlignUp(buffer.size(), 32));
|
||||
std::vector<u8> output(buffer.size());
|
||||
|
||||
mbedtls_aes_context aes_ctx;
|
||||
mbedtls_aes_setkey_enc(&aes_ctx, context.title_export.title_key.data(), 128);
|
||||
const int ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT, buffer.size(),
|
||||
iterator->second.iv.data(), buffer.data(), output.data());
|
||||
if (ret != 0)
|
||||
{
|
||||
// XXX: proper error code when IOSC_Encrypt fails.
|
||||
ERROR_LOG(IOS_ES, "ExportContentData: Failed to encrypt content.");
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
}
|
||||
const std::vector<u8> output =
|
||||
Common::AES::Encrypt(context.title_export.title_key.data(), iterator->second.iv.data(),
|
||||
buffer.data(), buffer.size());
|
||||
|
||||
Memory::CopyToEmu(request.io_vectors[0].address, output.data(), output.size());
|
||||
metadata.m_position += length;
|
||||
|
|
|
@ -169,15 +169,15 @@ IPCCommandResult FileIO::Seek(const SeekRequest& request)
|
|||
u32 new_position = 0;
|
||||
switch (request.mode)
|
||||
{
|
||||
case SeekRequest::IOS_SEEK_SET:
|
||||
case IOS_SEEK_SET:
|
||||
new_position = request.offset;
|
||||
break;
|
||||
|
||||
case SeekRequest::IOS_SEEK_CUR:
|
||||
case IOS_SEEK_CUR:
|
||||
new_position = m_SeekPos + request.offset;
|
||||
break;
|
||||
|
||||
case SeekRequest::IOS_SEEK_END:
|
||||
case IOS_SEEK_END:
|
||||
new_position = file_size + request.offset;
|
||||
break;
|
||||
|
||||
|
|
|
@ -546,6 +546,8 @@ void Kernel::DoState(PointerWrap& p)
|
|||
p.Do(m_ppc_uid);
|
||||
p.Do(m_ppc_gid);
|
||||
|
||||
m_iosc.DoState(p);
|
||||
|
||||
if (m_title_id == MIOS_TITLE_ID)
|
||||
return;
|
||||
|
||||
|
@ -615,6 +617,11 @@ void Kernel::DoState(PointerWrap& p)
|
|||
}
|
||||
}
|
||||
|
||||
IOSC& Kernel::GetIOSC()
|
||||
{
|
||||
return m_iosc;
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
s_event_enqueue = CoreTiming::RegisterEvent("IPCEvent", [](u64 userdata, s64) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "Common/CommonTypes.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/SystemTimers.h"
|
||||
#include "Core/IOS/IOSC.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
|
@ -55,6 +56,30 @@ enum IPCCommandType : u32
|
|||
IPC_REPLY = 8,
|
||||
};
|
||||
|
||||
enum ProcessId : u32
|
||||
{
|
||||
PID_KERNEL = 0,
|
||||
PID_ES = 1,
|
||||
PID_FS = 2,
|
||||
PID_DI = 3,
|
||||
PID_OH0 = 4,
|
||||
PID_OH1 = 5,
|
||||
PID_EHCI = 6,
|
||||
PID_SDI = 7,
|
||||
PID_USBETH = 8,
|
||||
PID_NET = 9,
|
||||
PID_WD = 10,
|
||||
PID_WL = 11,
|
||||
PID_KD = 12,
|
||||
PID_NCD = 13,
|
||||
PID_STM = 14,
|
||||
PID_PPCBOOT = 15,
|
||||
PID_SSL = 16,
|
||||
PID_USB = 17,
|
||||
PID_P2P = 18,
|
||||
PID_UNKNOWN = 19,
|
||||
};
|
||||
|
||||
// HLE for the IOS kernel: IPC, device management, syscalls, and Dolphin-specific, IOS-wide calls.
|
||||
class Kernel
|
||||
{
|
||||
|
@ -84,6 +109,8 @@ public:
|
|||
bool BootIOS(u64 ios_title_id);
|
||||
u32 GetVersion() const;
|
||||
|
||||
IOSC& GetIOSC();
|
||||
|
||||
private:
|
||||
void ExecuteIPCCommand(u32 address);
|
||||
IPCCommandResult HandleIPCCommand(const Request& request);
|
||||
|
@ -109,6 +136,8 @@ private:
|
|||
IPCMsgQueue m_reply_queue; // arm -> ppc
|
||||
IPCMsgQueue m_ack_queue; // arm -> ppc
|
||||
u64 m_last_reply_time = 0;
|
||||
|
||||
IOSC m_iosc;
|
||||
};
|
||||
|
||||
// Used for controlling and accessing an IOS instance that is tied to emulation.
|
||||
|
|
|
@ -0,0 +1,259 @@
|
|||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/Crypto/AES.h"
|
||||
#include "Common/Crypto/ec.h"
|
||||
#include "Core/IOS/Device.h"
|
||||
#include "Core/IOS/IOSC.h"
|
||||
#include "Core/ec_wii.h"
|
||||
|
||||
namespace IOS
|
||||
{
|
||||
namespace HLE
|
||||
{
|
||||
IOSC::IOSC()
|
||||
{
|
||||
LoadDefaultEntries();
|
||||
}
|
||||
|
||||
IOSC::~IOSC() = default;
|
||||
|
||||
ReturnCode IOSC::CreateObject(Handle* handle, ObjectType type, ObjectSubType subtype, u32 pid)
|
||||
{
|
||||
auto iterator = FindFreeEntry();
|
||||
if (iterator == m_key_entries.end())
|
||||
return IOSC_FAIL_ALLOC;
|
||||
|
||||
iterator->in_use = true;
|
||||
iterator->type = type;
|
||||
iterator->subtype = subtype;
|
||||
iterator->owner_mask = 1 << pid;
|
||||
|
||||
*handle = GetHandleFromIterator(iterator);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
ReturnCode IOSC::DeleteObject(Handle handle, u32 pid)
|
||||
{
|
||||
if (IsDefaultHandle(handle) || !HasOwnership(handle, pid))
|
||||
return IOSC_EACCES;
|
||||
|
||||
m_key_entries[handle].in_use = false;
|
||||
m_key_entries[handle].data.clear();
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
return IOSC_EACCES;
|
||||
}
|
||||
|
||||
auto* dest_entry = &m_key_entries[dest_handle];
|
||||
// TODO: allow other secret key subtypes
|
||||
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);
|
||||
}
|
||||
|
||||
constexpr size_t ECC233_PUBLIC_KEY_SIZE = 0x3c;
|
||||
ReturnCode IOSC::ImportPublicKey(Handle dest_handle, const u8* public_key, u32 pid)
|
||||
{
|
||||
if (!HasOwnership(dest_handle, pid) || IsDefaultHandle(dest_handle))
|
||||
return IOSC_EACCES;
|
||||
|
||||
auto* dest_entry = &m_key_entries[dest_handle];
|
||||
// TODO: allow other public key subtypes
|
||||
if (dest_entry->type != TYPE_PUBLIC_KEY || dest_entry->subtype != SUBTYPE_ECC233)
|
||||
return IOSC_INVALID_OBJTYPE;
|
||||
|
||||
dest_entry->data.assign(public_key, public_key + ECC233_PUBLIC_KEY_SIZE);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
ReturnCode IOSC::ComputeSharedKey(Handle dest_handle, Handle private_handle, Handle public_handle,
|
||||
u32 pid)
|
||||
{
|
||||
if (!HasOwnership(dest_handle, pid) || !HasOwnership(private_handle, pid) ||
|
||||
!HasOwnership(public_handle, pid) || IsDefaultHandle(dest_handle))
|
||||
{
|
||||
return IOSC_EACCES;
|
||||
}
|
||||
|
||||
auto* dest_entry = &m_key_entries[dest_handle];
|
||||
const auto* private_entry = &m_key_entries[private_handle];
|
||||
const auto* public_entry = &m_key_entries[public_handle];
|
||||
if (dest_entry->type != TYPE_SECRET_KEY || dest_entry->subtype != SUBTYPE_AES128 ||
|
||||
private_entry->type != TYPE_SECRET_KEY || private_entry->subtype != SUBTYPE_ECC233 ||
|
||||
public_entry->type != TYPE_PUBLIC_KEY || public_entry->subtype != SUBTYPE_ECC233)
|
||||
{
|
||||
return IOSC_INVALID_OBJTYPE;
|
||||
}
|
||||
|
||||
// Calculate the ECC shared secret.
|
||||
std::array<u8, 0x3c> shared_secret;
|
||||
point_mul(shared_secret.data(), private_entry->data.data(), public_entry->data.data());
|
||||
|
||||
std::array<u8, 20> sha1;
|
||||
mbedtls_sha1(shared_secret.data(), shared_secret.size() / 2, sha1.data());
|
||||
|
||||
dest_entry->data.resize(AES128_KEY_SIZE);
|
||||
std::copy_n(sha1.cbegin(), AES128_KEY_SIZE, dest_entry->data.begin());
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
ReturnCode IOSC::DecryptEncrypt(Common::AES::Mode mode, Handle key_handle, u8* iv, const u8* input,
|
||||
size_t size, u8* output, u32 pid) const
|
||||
{
|
||||
if (!HasOwnership(key_handle, pid))
|
||||
return IOSC_EACCES;
|
||||
|
||||
const auto* entry = &m_key_entries[key_handle];
|
||||
if (entry->type != TYPE_SECRET_KEY || entry->subtype != SUBTYPE_AES128)
|
||||
return IOSC_INVALID_OBJTYPE;
|
||||
|
||||
if (entry->data.size() != AES128_KEY_SIZE)
|
||||
return IOSC_FAIL_INTERNAL;
|
||||
|
||||
const std::vector<u8> data =
|
||||
Common::AES::DecryptEncrypt(entry->data.data(), iv, input, size, mode);
|
||||
|
||||
std::memcpy(output, data.data(), data.size());
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
ReturnCode IOSC::Encrypt(Handle key_handle, u8* iv, const u8* input, size_t size, u8* output,
|
||||
u32 pid) const
|
||||
{
|
||||
return DecryptEncrypt(Common::AES::Mode::Encrypt, key_handle, iv, input, size, output, pid);
|
||||
}
|
||||
|
||||
ReturnCode IOSC::Decrypt(Handle key_handle, u8* iv, const u8* input, size_t size, u8* output,
|
||||
u32 pid) const
|
||||
{
|
||||
return DecryptEncrypt(Common::AES::Mode::Decrypt, key_handle, iv, input, size, output, pid);
|
||||
}
|
||||
|
||||
ReturnCode IOSC::GetOwnership(Handle handle, u32* owner) const
|
||||
{
|
||||
if (handle < m_key_entries.size() && m_key_entries[handle].in_use)
|
||||
{
|
||||
*owner = m_key_entries[handle].owner_mask;
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
return IOSC_EINVAL;
|
||||
}
|
||||
|
||||
ReturnCode IOSC::SetOwnership(Handle handle, u32 new_owner, u32 pid)
|
||||
{
|
||||
if (!HasOwnership(handle, pid))
|
||||
return IOSC_EACCES;
|
||||
|
||||
m_key_entries[handle].owner_mask = new_owner;
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
void IOSC::LoadDefaultEntries()
|
||||
{
|
||||
// TODO: add support for loading and writing to a BootMii / SEEPROM and OTP dump.
|
||||
|
||||
const EcWii& ec = EcWii::GetInstance();
|
||||
|
||||
m_key_entries[HANDLE_CONSOLE_KEY] = {TYPE_SECRET_KEY, SUBTYPE_ECC233,
|
||||
std::vector<u8>(ec.GetNGPriv(), ec.GetNGPriv() + 30), 3};
|
||||
|
||||
// Unimplemented.
|
||||
m_key_entries[HANDLE_CONSOLE_ID] = {TYPE_DATA, SUBTYPE_DATA, std::vector<u8>(4), 0xFFFFFFF};
|
||||
m_key_entries[HANDLE_FS_KEY] = {TYPE_SECRET_KEY, SUBTYPE_AES128, std::vector<u8>(16), 5};
|
||||
m_key_entries[HANDLE_FS_MAC] = {TYPE_SECRET_KEY, SUBTYPE_MAC, std::vector<u8>(20), 5};
|
||||
|
||||
m_key_entries[HANDLE_COMMON_KEY] = {TYPE_SECRET_KEY,
|
||||
SUBTYPE_AES128,
|
||||
{{0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4, 0x48, 0xd9,
|
||||
0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7}},
|
||||
3};
|
||||
|
||||
// Unimplemented.
|
||||
m_key_entries[HANDLE_PRNG_KEY] = {TYPE_SECRET_KEY, SUBTYPE_AES128, std::vector<u8>(16), 3};
|
||||
|
||||
m_key_entries[HANDLE_SD_KEY] = {TYPE_SECRET_KEY,
|
||||
SUBTYPE_AES128,
|
||||
{{0xab, 0x01, 0xb9, 0xd8, 0xe1, 0x62, 0x2b, 0x08, 0xaf, 0xba,
|
||||
0xd8, 0x4d, 0xbf, 0xc2, 0xa5, 0x5d}},
|
||||
3};
|
||||
|
||||
// Unimplemented.
|
||||
m_key_entries[HANDLE_BOOT2_VERSION] = {TYPE_DATA, SUBTYPE_VERSION, std::vector<u8>(4), 3};
|
||||
m_key_entries[HANDLE_UNKNOWN_8] = {TYPE_DATA, SUBTYPE_VERSION, std::vector<u8>(4), 3};
|
||||
m_key_entries[HANDLE_UNKNOWN_9] = {TYPE_DATA, SUBTYPE_VERSION, std::vector<u8>(4), 3};
|
||||
m_key_entries[HANDLE_FS_VERSION] = {TYPE_DATA, SUBTYPE_VERSION, std::vector<u8>(4), 3};
|
||||
|
||||
m_key_entries[HANDLE_NEW_COMMON_KEY] = {TYPE_SECRET_KEY,
|
||||
SUBTYPE_AES128,
|
||||
{{0x63, 0xb8, 0x2b, 0xb4, 0xf4, 0x61, 0x4e, 0x2e, 0x13,
|
||||
0xf2, 0xfe, 0xfb, 0xba, 0x4c, 0x9b, 0x7e}},
|
||||
3};
|
||||
}
|
||||
|
||||
IOSC::KeyEntry::KeyEntry() = default;
|
||||
|
||||
IOSC::KeyEntry::KeyEntry(ObjectType type_, ObjectSubType subtype_, std::vector<u8>&& data_,
|
||||
u32 owner_mask_)
|
||||
: in_use(true), type(type_), subtype(subtype_), data(std::move(data_)), owner_mask(owner_mask_)
|
||||
{
|
||||
}
|
||||
|
||||
IOSC::KeyEntries::iterator IOSC::FindFreeEntry()
|
||||
{
|
||||
return std::find_if(m_key_entries.begin(), m_key_entries.end(),
|
||||
[](const auto& entry) { return !entry.in_use; });
|
||||
}
|
||||
|
||||
IOSC::Handle IOSC::GetHandleFromIterator(IOSC::KeyEntries::iterator iterator) const
|
||||
{
|
||||
_assert_(iterator != m_key_entries.end());
|
||||
return static_cast<Handle>(iterator - m_key_entries.begin());
|
||||
}
|
||||
|
||||
bool IOSC::HasOwnership(Handle handle, u32 pid) const
|
||||
{
|
||||
u32 owner_mask;
|
||||
return GetOwnership(handle, &owner_mask) == IPC_SUCCESS && ((1 << pid) & owner_mask) != 0;
|
||||
}
|
||||
|
||||
bool IOSC::IsDefaultHandle(Handle handle) const
|
||||
{
|
||||
constexpr Handle last_default_handle = HANDLE_NEW_COMMON_KEY;
|
||||
return handle <= last_default_handle;
|
||||
}
|
||||
|
||||
void IOSC::DoState(PointerWrap& p)
|
||||
{
|
||||
for (auto& entry : m_key_entries)
|
||||
entry.DoState(p);
|
||||
}
|
||||
|
||||
void IOSC::KeyEntry::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(in_use);
|
||||
p.Do(type);
|
||||
p.Do(subtype);
|
||||
p.Do(data);
|
||||
p.Do(owner_mask);
|
||||
}
|
||||
} // namespace HLE
|
||||
} // namespace IOS
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Implementation of an IOSC-like API, but much simpler since we only support actual keys.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Crypto/AES.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
namespace IOS
|
||||
{
|
||||
namespace HLE
|
||||
{
|
||||
enum ReturnCode : s32;
|
||||
|
||||
class IOSC final
|
||||
{
|
||||
public:
|
||||
IOSC();
|
||||
~IOSC();
|
||||
|
||||
using Handle = u32;
|
||||
// We use the same default key handle IDs as the actual IOSC because there are ioctlvs
|
||||
// that accept arbitrary key handles from the PPC, so the IDs must match.
|
||||
// More information on default handles: https://wiibrew.org/wiki/IOS/Syscalls
|
||||
enum DefaultHandle : u32
|
||||
{
|
||||
// ECC-233 private signing key (per-console)
|
||||
HANDLE_CONSOLE_KEY = 0,
|
||||
// Console ID
|
||||
HANDLE_CONSOLE_ID = 1,
|
||||
// NAND FS AES-128 key
|
||||
HANDLE_FS_KEY = 2,
|
||||
// NAND FS HMAC
|
||||
HANDLE_FS_MAC = 3,
|
||||
// Common key
|
||||
HANDLE_COMMON_KEY = 4,
|
||||
// PRNG seed
|
||||
HANDLE_PRNG_KEY = 5,
|
||||
// SD AES-128 key
|
||||
HANDLE_SD_KEY = 6,
|
||||
// boot2 version (writable)
|
||||
HANDLE_BOOT2_VERSION = 7,
|
||||
// Unknown
|
||||
HANDLE_UNKNOWN_8 = 8,
|
||||
// Unknown
|
||||
HANDLE_UNKNOWN_9 = 9,
|
||||
// Filesystem version (writable)
|
||||
HANDLE_FS_VERSION = 10,
|
||||
// New common key (aka Korean common key)
|
||||
HANDLE_NEW_COMMON_KEY = 11,
|
||||
};
|
||||
|
||||
enum ObjectType : u8
|
||||
{
|
||||
TYPE_SECRET_KEY = 0,
|
||||
TYPE_PUBLIC_KEY = 1,
|
||||
TYPE_DATA = 3,
|
||||
};
|
||||
|
||||
enum ObjectSubType : u8
|
||||
{
|
||||
SUBTYPE_AES128 = 0,
|
||||
SUBTYPE_MAC = 1,
|
||||
SUBTYPE_ECC233 = 4,
|
||||
SUBTYPE_DATA = 5,
|
||||
SUBTYPE_VERSION = 6
|
||||
};
|
||||
|
||||
// Create an object for use with the other functions that operate on objects.
|
||||
ReturnCode CreateObject(Handle* handle, ObjectType type, ObjectSubType subtype, u32 pid);
|
||||
// Delete an object. Built-in objects cannot be deleted.
|
||||
ReturnCode DeleteObject(Handle handle, u32 pid);
|
||||
// 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 public key.
|
||||
ReturnCode ImportPublicKey(Handle dest_handle, const u8* public_key, u32 pid);
|
||||
// Compute an AES key from an ECDH shared secret.
|
||||
ReturnCode ComputeSharedKey(Handle dest_handle, Handle private_handle, Handle public_handle,
|
||||
u32 pid);
|
||||
|
||||
// AES encrypt/decrypt.
|
||||
ReturnCode Encrypt(Handle key_handle, u8* iv, const u8* input, size_t size, u8* output,
|
||||
u32 pid) const;
|
||||
ReturnCode Decrypt(Handle key_handle, u8* iv, const u8* input, size_t size, u8* output,
|
||||
u32 pid) const;
|
||||
|
||||
// Ownership
|
||||
ReturnCode GetOwnership(Handle handle, u32* owner) const;
|
||||
ReturnCode SetOwnership(Handle handle, u32 owner, u32 pid);
|
||||
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
private:
|
||||
struct KeyEntry
|
||||
{
|
||||
KeyEntry();
|
||||
KeyEntry(ObjectType type_, ObjectSubType subtype_, std::vector<u8>&& data_, u32 owner_mask_);
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
bool in_use = false;
|
||||
ObjectType type;
|
||||
ObjectSubType subtype;
|
||||
std::vector<u8> data;
|
||||
u32 owner_mask = 0;
|
||||
};
|
||||
// The Wii's IOSC is limited to 32 entries, including 12 built-in entries.
|
||||
using KeyEntries = std::array<KeyEntry, 32>;
|
||||
|
||||
void LoadDefaultEntries();
|
||||
KeyEntries::iterator FindFreeEntry();
|
||||
Handle GetHandleFromIterator(KeyEntries::iterator iterator) const;
|
||||
bool HasOwnership(Handle handle, u32 pid) const;
|
||||
bool IsDefaultHandle(Handle handle) const;
|
||||
ReturnCode DecryptEncrypt(Common::AES::Mode mode, Handle key_handle, u8* iv, const u8* input,
|
||||
size_t size, u8* output, u32 pid) const;
|
||||
|
||||
KeyEntries m_key_entries;
|
||||
};
|
||||
} // namespace HLE
|
||||
} // namespace IOS
|
|
@ -71,7 +71,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
|
|||
static std::thread g_save_thread;
|
||||
|
||||
// Don't forget to increase this after doing changes on the savestate system
|
||||
static const u32 STATE_VERSION = 83; // Last changed in PR 5340
|
||||
static const u32 STATE_VERSION = 84; // Last changed in PR 5354
|
||||
|
||||
// Maps savestate versions to Dolphin versions.
|
||||
// Versions after 42 don't need to be added to this list,
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include "Core/ec_wii.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
|
@ -180,19 +179,6 @@ const u8* EcWii::GetNGSig() const
|
|||
return BootMiiKeysBin.ng_sig;
|
||||
}
|
||||
|
||||
std::array<u8, 16> EcWii::GetSharedSecret(const EcWii::ECCKey& peer_public_key) const
|
||||
{
|
||||
EcWii::ECCKey shared_secret;
|
||||
point_mul(shared_secret.data(), GetNGPriv(), peer_public_key.data());
|
||||
|
||||
std::array<u8, 20> sha1;
|
||||
mbedtls_sha1(shared_secret.data(), shared_secret.size() / 2, sha1.data());
|
||||
|
||||
std::array<u8, 16> aes_key;
|
||||
std::copy_n(sha1.cbegin(), aes_key.size(), aes_key.begin());
|
||||
return aes_key;
|
||||
}
|
||||
|
||||
void EcWii::InitDefaults()
|
||||
{
|
||||
memset(&BootMiiKeysBin, 0, sizeof(BootMiiKeysBin));
|
||||
|
|
|
@ -26,8 +26,6 @@
|
|||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
void MakeNGCert(u8* ng_cert_out, u32 NG_id, u32 NG_key_id, const u8* NG_priv, const u8* NG_sig);
|
||||
void MakeAPSigAndCert(u8* sig_out, u8* ap_cert_out, u64 title_id, u8* data, u32 data_size,
|
||||
const u8* NG_priv, u32 NG_id);
|
||||
|
@ -43,9 +41,6 @@ public:
|
|||
const u8* GetNGPriv() const;
|
||||
const u8* GetNGSig() const;
|
||||
|
||||
using ECCKey = std::array<u8, 0x3c>;
|
||||
std::array<u8, 16> GetSharedSecret(const ECCKey& peer_public_key) const;
|
||||
|
||||
private:
|
||||
void InitDefaults();
|
||||
|
||||
|
|
Loading…
Reference in New Issue