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