From e01624f64bc7c1aa1fa5f70f7ef1f96de5b7778f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 30 Apr 2017 18:42:11 +0200 Subject: [PATCH 1/3] IOS: Add/move some useful enums/structs --- Source/Core/Core/IOS/Device.h | 15 +++++++++------ Source/Core/Core/IOS/FS/FileIO.cpp | 6 +++--- Source/Core/Core/IOS/IOS.h | 24 ++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 9 deletions(-) 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/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.h b/Source/Core/Core/IOS/IOS.h index 40db6c09b5..d9c6d8e785 100644 --- a/Source/Core/Core/IOS/IOS.h +++ b/Source/Core/Core/IOS/IOS.h @@ -55,6 +55,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 { From f8fb9e2d034d122c72515083f75e2034abf6210d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Mon, 1 May 2017 17:50:12 +0200 Subject: [PATCH 2/3] IOS: Implement IOSC-like API This prevents the IOS crypto code and keys from being spread over the codebase. Things only have to be implemented once, and can be used everywhere from the IOS code. Additionally, since ES exposes some IOSC calls directly (DeleteObject and Encrypt/Decrypt), we need this for proper emulation. Currently, this only supports AES key objects. --- Source/Core/Common/Crypto/AES.cpp | 21 ++- Source/Core/Common/Crypto/AES.h | 9 + Source/Core/Core/CMakeLists.txt | 1 + Source/Core/Core/Core.vcxproj | 2 + Source/Core/Core/Core.vcxproj.filters | 6 + Source/Core/Core/IOS/IOSC.cpp | 259 ++++++++++++++++++++++++++ Source/Core/Core/IOS/IOSC.h | 130 +++++++++++++ 7 files changed, 425 insertions(+), 3 deletions(-) create mode 100644 Source/Core/Core/IOS/IOSC.cpp create mode 100644 Source/Core/Core/IOS/IOSC.h 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 a68a87d731..8261ad8687 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/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 From 08f6c31287b22b94172dc6b8e5dfb32d4ab99974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Mon, 1 May 2017 19:20:50 +0200 Subject: [PATCH 3/3] IOS: Reuse more code for crypto operations This changes some parts of IOS (actually just ES) to reuse more crypto code from IOSC or Common::AES. TicketReader still returns the title key directly as opposed to having ES use IOSC directly to avoid duplicating the title key IV stuff. Side effects: * A nasty unbounded array access bug is now fixed. * ES_Decrypt/ES_Encrypt now returns sane results for keys other than the SD key. * Titles with a Korean ticket can now be decrypted properly. And in the future, we can look into implementing ioctlv 0x3c and 0x3d now that we have the proper "infra" for IOSC calls. --- Source/Core/Core/IOS/ES/ES.h | 3 -- Source/Core/Core/IOS/ES/Formats.cpp | 53 ++++++++++++------- Source/Core/Core/IOS/ES/Identity.cpp | 56 +++------------------ Source/Core/Core/IOS/ES/TitleManagement.cpp | 25 +++------ Source/Core/Core/IOS/IOS.cpp | 7 +++ Source/Core/Core/IOS/IOS.h | 5 ++ Source/Core/Core/State.cpp | 2 +- Source/Core/Core/ec_wii.cpp | 14 ------ Source/Core/Core/ec_wii.h | 5 -- 9 files changed, 63 insertions(+), 107 deletions(-) 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/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 d9c6d8e785..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; @@ -108,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); @@ -133,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/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();