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] 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