diff --git a/Source/Core/Common/Crypto/AES.cpp b/Source/Core/Common/Crypto/AES.cpp index e12ba2481c..00685beba4 100644 --- a/Source/Core/Common/Crypto/AES.cpp +++ b/Source/Core/Common/Crypto/AES.cpp @@ -10,15 +10,30 @@ namespace Common { namespace AES { -std::vector Decrypt(const u8* key, u8* iv, const u8* src, size_t size) +std::vector DecryptEncrypt(const u8* key, u8* iv, const u8* src, size_t size, Mode mode) { mbedtls_aes_context aes_ctx; std::vector 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 Decrypt(const u8* key, u8* iv, const u8* src, size_t size) +{ + return DecryptEncrypt(key, iv, src, size, Mode::Decrypt); +} + +std::vector Encrypt(const u8* key, u8* iv, const u8* src, size_t size) +{ + return DecryptEncrypt(key, iv, src, size, Mode::Encrypt); +} } // namespace AES } // namespace Common diff --git a/Source/Core/Common/Crypto/AES.h b/Source/Core/Common/Crypto/AES.h index 54d88727ac..3122f5fea7 100644 --- a/Source/Core/Common/Crypto/AES.h +++ b/Source/Core/Common/Crypto/AES.h @@ -13,6 +13,15 @@ namespace Common { namespace AES { +enum class Mode +{ + Decrypt, + Encrypt, +}; +std::vector DecryptEncrypt(const u8* key, u8* iv, const u8* src, size_t size, Mode mode); + +// Convenience functions std::vector Decrypt(const u8* key, u8* iv, const u8* src, size_t size); +std::vector Encrypt(const u8* key, u8* iv, const u8* src, size_t size); } // namespace AES } // namespace Common diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index bbb2609b40..28e10f52ae 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -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 diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index 49a46413f9..03cdf20422 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -175,6 +175,7 @@ + @@ -432,6 +433,7 @@ + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 57984fa078..012004e38c 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -793,6 +793,9 @@ IOS + + IOS + IOS @@ -1507,6 +1510,9 @@ IOS + + IOS + IOS diff --git a/Source/Core/Core/IOS/Device.h b/Source/Core/Core/IOS/Device.h index c3ca6812c0..13aa85a316 100644 --- a/Source/Core/Core/IOS/Device.h +++ b/Source/Core/Core/IOS/Device.h @@ -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); diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index ea3b5de9d4..498b9c7e71 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -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; diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index 2c91dc923f..ad80683739 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -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 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 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 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 iv{}; std::copy_n(ticket_begin + offsetof(Ticket, ticket_id), sizeof(Ticket::ticket_id), iv.begin()); - const std::vector key = - Common::AES::Decrypt(shared_secret.data(), iv.data(), - &*ticket_begin + offsetof(Ticket, title_key), sizeof(Ticket::title_key)); - + std::array 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 diff --git a/Source/Core/Core/IOS/ES/Identity.cpp b/Source/Core/Core/IOS/ES/Identity.cpp index 3ae6ba5083..832bd302bc 100644 --- a/Source/Core/Core/IOS/ES/Identity.cpp +++ b/Source/Core/Core/IOS/ES/Identity.cpp @@ -7,8 +7,6 @@ #include #include -#include - #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) diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 8426b15582..93dfbee95c 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -9,10 +9,10 @@ #include #include -#include #include #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 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 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 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 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; diff --git a/Source/Core/Core/IOS/FS/FileIO.cpp b/Source/Core/Core/IOS/FS/FileIO.cpp index dd2f17af60..fd0361bc64 100644 --- a/Source/Core/Core/IOS/FS/FileIO.cpp +++ b/Source/Core/Core/IOS/FS/FileIO.cpp @@ -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; diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index 5fce675168..8720882bac 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -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) { diff --git a/Source/Core/Core/IOS/IOS.h b/Source/Core/Core/IOS/IOS.h index 40db6c09b5..ffc469c882 100644 --- a/Source/Core/Core/IOS/IOS.h +++ b/Source/Core/Core/IOS/IOS.h @@ -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. diff --git a/Source/Core/Core/IOS/IOSC.cpp b/Source/Core/Core/IOS/IOSC.cpp new file mode 100644 index 0000000000..6157a7b6a9 --- /dev/null +++ b/Source/Core/Core/IOS/IOSC.cpp @@ -0,0 +1,259 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include + +#include + +#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 shared_secret; + point_mul(shared_secret.data(), private_entry->data.data(), public_entry->data.data()); + + std::array 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 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(ec.GetNGPriv(), ec.GetNGPriv() + 30), 3}; + + // Unimplemented. + m_key_entries[HANDLE_CONSOLE_ID] = {TYPE_DATA, SUBTYPE_DATA, std::vector(4), 0xFFFFFFF}; + m_key_entries[HANDLE_FS_KEY] = {TYPE_SECRET_KEY, SUBTYPE_AES128, std::vector(16), 5}; + m_key_entries[HANDLE_FS_MAC] = {TYPE_SECRET_KEY, SUBTYPE_MAC, std::vector(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(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(4), 3}; + m_key_entries[HANDLE_UNKNOWN_8] = {TYPE_DATA, SUBTYPE_VERSION, std::vector(4), 3}; + m_key_entries[HANDLE_UNKNOWN_9] = {TYPE_DATA, SUBTYPE_VERSION, std::vector(4), 3}; + m_key_entries[HANDLE_FS_VERSION] = {TYPE_DATA, SUBTYPE_VERSION, std::vector(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&& 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(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 diff --git a/Source/Core/Core/IOS/IOSC.h b/Source/Core/Core/IOS/IOSC.h new file mode 100644 index 0000000000..5433251ba7 --- /dev/null +++ b/Source/Core/Core/IOS/IOSC.h @@ -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 +#include +#include + +#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&& data_, u32 owner_mask_); + void DoState(PointerWrap& p); + + bool in_use = false; + ObjectType type; + ObjectSubType subtype; + std::vector data; + u32 owner_mask = 0; + }; + // The Wii's IOSC is limited to 32 entries, including 12 built-in entries. + using KeyEntries = std::array; + + 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 diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 25b65dc8a7..064f959223 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -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, diff --git a/Source/Core/Core/ec_wii.cpp b/Source/Core/Core/ec_wii.cpp index e726cf0699..fc3b031e49 100644 --- a/Source/Core/Core/ec_wii.cpp +++ b/Source/Core/Core/ec_wii.cpp @@ -9,7 +9,6 @@ #include "Core/ec_wii.h" -#include #include #include @@ -180,19 +179,6 @@ const u8* EcWii::GetNGSig() const return BootMiiKeysBin.ng_sig; } -std::array 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 sha1; - mbedtls_sha1(shared_secret.data(), shared_secret.size() / 2, sha1.data()); - - std::array aes_key; - std::copy_n(sha1.cbegin(), aes_key.size(), aes_key.begin()); - return aes_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 a51e44a881..99b1bafeaf 100644 --- a/Source/Core/Core/ec_wii.h +++ b/Source/Core/Core/ec_wii.h @@ -26,8 +26,6 @@ #include "Common/CommonTypes.h" -#include - 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; - std::array GetSharedSecret(const ECCKey& peer_public_key) const; - private: void InitDefaults();