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.
This commit is contained in:
Léo Lam 2017-05-01 17:50:12 +02:00
parent e01624f64b
commit f8fb9e2d03
7 changed files with 425 additions and 3 deletions

View File

@ -10,15 +10,30 @@ namespace Common
{
namespace AES
{
std::vector<u8> Decrypt(const u8* key, u8* iv, const u8* src, size_t size)
std::vector<u8> DecryptEncrypt(const u8* key, u8* iv, const u8* src, size_t size, Mode mode)
{
mbedtls_aes_context aes_ctx;
std::vector<u8> buffer(size);
mbedtls_aes_setkey_dec(&aes_ctx, key, 128);
mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, size, iv, src, buffer.data());
if (mode == Mode::Encrypt)
mbedtls_aes_setkey_enc(&aes_ctx, key, 128);
else
mbedtls_aes_setkey_dec(&aes_ctx, key, 128);
mbedtls_aes_crypt_cbc(&aes_ctx, mode == Mode::Encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT,
size, iv, src, buffer.data());
return buffer;
}
std::vector<u8> Decrypt(const u8* key, u8* iv, const u8* src, size_t size)
{
return DecryptEncrypt(key, iv, src, size, Mode::Decrypt);
}
std::vector<u8> Encrypt(const u8* key, u8* iv, const u8* src, size_t size)
{
return DecryptEncrypt(key, iv, src, size, Mode::Encrypt);
}
} // namespace AES
} // namespace Common

View File

@ -13,6 +13,15 @@ namespace Common
{
namespace AES
{
enum class Mode
{
Decrypt,
Encrypt,
};
std::vector<u8> DecryptEncrypt(const u8* key, u8* iv, const u8* src, size_t size, Mode mode);
// Convenience functions
std::vector<u8> Decrypt(const u8* key, u8* iv, const u8* src, size_t size);
std::vector<u8> Encrypt(const u8* key, u8* iv, const u8* src, size_t size);
} // namespace AES
} // namespace Common

View File

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

View File

@ -175,6 +175,7 @@
<ClCompile Include="IOS\Device.cpp" />
<ClCompile Include="IOS\DeviceStub.cpp" />
<ClCompile Include="IOS\IOS.cpp" />
<ClCompile Include="IOS\IOSC.cpp" />
<ClCompile Include="IOS\MemoryValues.cpp" />
<ClCompile Include="IOS\MIOS.cpp" />
<ClCompile Include="IOS\DI\DI.cpp" />
@ -432,6 +433,7 @@
<ClInclude Include="IOS\Device.h" />
<ClInclude Include="IOS\DeviceStub.h" />
<ClInclude Include="IOS\IOS.h" />
<ClInclude Include="IOS\IOSC.h" />
<ClInclude Include="IOS\MemoryValues.h" />
<ClInclude Include="IOS\MIOS.h" />
<ClInclude Include="IOS\DI\DI.h" />

View File

@ -793,6 +793,9 @@
<ClCompile Include="IOS\IOS.cpp">
<Filter>IOS</Filter>
</ClCompile>
<ClCompile Include="IOS\IOSC.cpp">
<Filter>IOS</Filter>
</ClCompile>
<ClCompile Include="IOS\MemoryValues.cpp">
<Filter>IOS</Filter>
</ClCompile>
@ -1507,6 +1510,9 @@
<ClInclude Include="IOS\IOS.h">
<Filter>IOS</Filter>
</ClInclude>
<ClInclude Include="IOS\IOSC.h">
<Filter>IOS</Filter>
</ClInclude>
<ClInclude Include="IOS\MemoryValues.h">
<Filter>IOS</Filter>
</ClInclude>

View File

@ -0,0 +1,259 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <cstring>
#include <mbedtls/sha1.h>
#include "Common/Assert.h"
#include "Common/ChunkFile.h"
#include "Common/Crypto/AES.h"
#include "Common/Crypto/ec.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/IOSC.h"
#include "Core/ec_wii.h"
namespace IOS
{
namespace HLE
{
IOSC::IOSC()
{
LoadDefaultEntries();
}
IOSC::~IOSC() = default;
ReturnCode IOSC::CreateObject(Handle* handle, ObjectType type, ObjectSubType subtype, u32 pid)
{
auto iterator = FindFreeEntry();
if (iterator == m_key_entries.end())
return IOSC_FAIL_ALLOC;
iterator->in_use = true;
iterator->type = type;
iterator->subtype = subtype;
iterator->owner_mask = 1 << pid;
*handle = GetHandleFromIterator(iterator);
return IPC_SUCCESS;
}
ReturnCode IOSC::DeleteObject(Handle handle, u32 pid)
{
if (IsDefaultHandle(handle) || !HasOwnership(handle, pid))
return IOSC_EACCES;
m_key_entries[handle].in_use = false;
m_key_entries[handle].data.clear();
return IPC_SUCCESS;
}
constexpr size_t AES128_KEY_SIZE = 0x10;
ReturnCode IOSC::ImportSecretKey(Handle dest_handle, Handle decrypt_handle, u8* iv,
const u8* encrypted_key, u32 pid)
{
if (!HasOwnership(dest_handle, pid) || !HasOwnership(decrypt_handle, pid) ||
IsDefaultHandle(dest_handle))
{
return IOSC_EACCES;
}
auto* dest_entry = &m_key_entries[dest_handle];
// TODO: allow other secret key subtypes
if (dest_entry->type != TYPE_SECRET_KEY || dest_entry->subtype != SUBTYPE_AES128)
return IOSC_INVALID_OBJTYPE;
dest_entry->data.resize(AES128_KEY_SIZE);
return Decrypt(decrypt_handle, iv, encrypted_key, AES128_KEY_SIZE, dest_entry->data.data(), pid);
}
constexpr size_t ECC233_PUBLIC_KEY_SIZE = 0x3c;
ReturnCode IOSC::ImportPublicKey(Handle dest_handle, const u8* public_key, u32 pid)
{
if (!HasOwnership(dest_handle, pid) || IsDefaultHandle(dest_handle))
return IOSC_EACCES;
auto* dest_entry = &m_key_entries[dest_handle];
// TODO: allow other public key subtypes
if (dest_entry->type != TYPE_PUBLIC_KEY || dest_entry->subtype != SUBTYPE_ECC233)
return IOSC_INVALID_OBJTYPE;
dest_entry->data.assign(public_key, public_key + ECC233_PUBLIC_KEY_SIZE);
return IPC_SUCCESS;
}
ReturnCode IOSC::ComputeSharedKey(Handle dest_handle, Handle private_handle, Handle public_handle,
u32 pid)
{
if (!HasOwnership(dest_handle, pid) || !HasOwnership(private_handle, pid) ||
!HasOwnership(public_handle, pid) || IsDefaultHandle(dest_handle))
{
return IOSC_EACCES;
}
auto* dest_entry = &m_key_entries[dest_handle];
const auto* private_entry = &m_key_entries[private_handle];
const auto* public_entry = &m_key_entries[public_handle];
if (dest_entry->type != TYPE_SECRET_KEY || dest_entry->subtype != SUBTYPE_AES128 ||
private_entry->type != TYPE_SECRET_KEY || private_entry->subtype != SUBTYPE_ECC233 ||
public_entry->type != TYPE_PUBLIC_KEY || public_entry->subtype != SUBTYPE_ECC233)
{
return IOSC_INVALID_OBJTYPE;
}
// Calculate the ECC shared secret.
std::array<u8, 0x3c> shared_secret;
point_mul(shared_secret.data(), private_entry->data.data(), public_entry->data.data());
std::array<u8, 20> sha1;
mbedtls_sha1(shared_secret.data(), shared_secret.size() / 2, sha1.data());
dest_entry->data.resize(AES128_KEY_SIZE);
std::copy_n(sha1.cbegin(), AES128_KEY_SIZE, dest_entry->data.begin());
return IPC_SUCCESS;
}
ReturnCode IOSC::DecryptEncrypt(Common::AES::Mode mode, Handle key_handle, u8* iv, const u8* input,
size_t size, u8* output, u32 pid) const
{
if (!HasOwnership(key_handle, pid))
return IOSC_EACCES;
const auto* entry = &m_key_entries[key_handle];
if (entry->type != TYPE_SECRET_KEY || entry->subtype != SUBTYPE_AES128)
return IOSC_INVALID_OBJTYPE;
if (entry->data.size() != AES128_KEY_SIZE)
return IOSC_FAIL_INTERNAL;
const std::vector<u8> data =
Common::AES::DecryptEncrypt(entry->data.data(), iv, input, size, mode);
std::memcpy(output, data.data(), data.size());
return IPC_SUCCESS;
}
ReturnCode IOSC::Encrypt(Handle key_handle, u8* iv, const u8* input, size_t size, u8* output,
u32 pid) const
{
return DecryptEncrypt(Common::AES::Mode::Encrypt, key_handle, iv, input, size, output, pid);
}
ReturnCode IOSC::Decrypt(Handle key_handle, u8* iv, const u8* input, size_t size, u8* output,
u32 pid) const
{
return DecryptEncrypt(Common::AES::Mode::Decrypt, key_handle, iv, input, size, output, pid);
}
ReturnCode IOSC::GetOwnership(Handle handle, u32* owner) const
{
if (handle < m_key_entries.size() && m_key_entries[handle].in_use)
{
*owner = m_key_entries[handle].owner_mask;
return IPC_SUCCESS;
}
return IOSC_EINVAL;
}
ReturnCode IOSC::SetOwnership(Handle handle, u32 new_owner, u32 pid)
{
if (!HasOwnership(handle, pid))
return IOSC_EACCES;
m_key_entries[handle].owner_mask = new_owner;
return IPC_SUCCESS;
}
void IOSC::LoadDefaultEntries()
{
// TODO: add support for loading and writing to a BootMii / SEEPROM and OTP dump.
const EcWii& ec = EcWii::GetInstance();
m_key_entries[HANDLE_CONSOLE_KEY] = {TYPE_SECRET_KEY, SUBTYPE_ECC233,
std::vector<u8>(ec.GetNGPriv(), ec.GetNGPriv() + 30), 3};
// Unimplemented.
m_key_entries[HANDLE_CONSOLE_ID] = {TYPE_DATA, SUBTYPE_DATA, std::vector<u8>(4), 0xFFFFFFF};
m_key_entries[HANDLE_FS_KEY] = {TYPE_SECRET_KEY, SUBTYPE_AES128, std::vector<u8>(16), 5};
m_key_entries[HANDLE_FS_MAC] = {TYPE_SECRET_KEY, SUBTYPE_MAC, std::vector<u8>(20), 5};
m_key_entries[HANDLE_COMMON_KEY] = {TYPE_SECRET_KEY,
SUBTYPE_AES128,
{{0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4, 0x48, 0xd9,
0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7}},
3};
// Unimplemented.
m_key_entries[HANDLE_PRNG_KEY] = {TYPE_SECRET_KEY, SUBTYPE_AES128, std::vector<u8>(16), 3};
m_key_entries[HANDLE_SD_KEY] = {TYPE_SECRET_KEY,
SUBTYPE_AES128,
{{0xab, 0x01, 0xb9, 0xd8, 0xe1, 0x62, 0x2b, 0x08, 0xaf, 0xba,
0xd8, 0x4d, 0xbf, 0xc2, 0xa5, 0x5d}},
3};
// Unimplemented.
m_key_entries[HANDLE_BOOT2_VERSION] = {TYPE_DATA, SUBTYPE_VERSION, std::vector<u8>(4), 3};
m_key_entries[HANDLE_UNKNOWN_8] = {TYPE_DATA, SUBTYPE_VERSION, std::vector<u8>(4), 3};
m_key_entries[HANDLE_UNKNOWN_9] = {TYPE_DATA, SUBTYPE_VERSION, std::vector<u8>(4), 3};
m_key_entries[HANDLE_FS_VERSION] = {TYPE_DATA, SUBTYPE_VERSION, std::vector<u8>(4), 3};
m_key_entries[HANDLE_NEW_COMMON_KEY] = {TYPE_SECRET_KEY,
SUBTYPE_AES128,
{{0x63, 0xb8, 0x2b, 0xb4, 0xf4, 0x61, 0x4e, 0x2e, 0x13,
0xf2, 0xfe, 0xfb, 0xba, 0x4c, 0x9b, 0x7e}},
3};
}
IOSC::KeyEntry::KeyEntry() = default;
IOSC::KeyEntry::KeyEntry(ObjectType type_, ObjectSubType subtype_, std::vector<u8>&& data_,
u32 owner_mask_)
: in_use(true), type(type_), subtype(subtype_), data(std::move(data_)), owner_mask(owner_mask_)
{
}
IOSC::KeyEntries::iterator IOSC::FindFreeEntry()
{
return std::find_if(m_key_entries.begin(), m_key_entries.end(),
[](const auto& entry) { return !entry.in_use; });
}
IOSC::Handle IOSC::GetHandleFromIterator(IOSC::KeyEntries::iterator iterator) const
{
_assert_(iterator != m_key_entries.end());
return static_cast<Handle>(iterator - m_key_entries.begin());
}
bool IOSC::HasOwnership(Handle handle, u32 pid) const
{
u32 owner_mask;
return GetOwnership(handle, &owner_mask) == IPC_SUCCESS && ((1 << pid) & owner_mask) != 0;
}
bool IOSC::IsDefaultHandle(Handle handle) const
{
constexpr Handle last_default_handle = HANDLE_NEW_COMMON_KEY;
return handle <= last_default_handle;
}
void IOSC::DoState(PointerWrap& p)
{
for (auto& entry : m_key_entries)
entry.DoState(p);
}
void IOSC::KeyEntry::DoState(PointerWrap& p)
{
p.Do(in_use);
p.Do(type);
p.Do(subtype);
p.Do(data);
p.Do(owner_mask);
}
} // namespace HLE
} // namespace IOS

130
Source/Core/Core/IOS/IOSC.h Normal file
View File

@ -0,0 +1,130 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
// Implementation of an IOSC-like API, but much simpler since we only support actual keys.
#pragma once
#include <array>
#include <map>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/Crypto/AES.h"
class PointerWrap;
namespace IOS
{
namespace HLE
{
enum ReturnCode : s32;
class IOSC final
{
public:
IOSC();
~IOSC();
using Handle = u32;
// We use the same default key handle IDs as the actual IOSC because there are ioctlvs
// that accept arbitrary key handles from the PPC, so the IDs must match.
// More information on default handles: https://wiibrew.org/wiki/IOS/Syscalls
enum DefaultHandle : u32
{
// ECC-233 private signing key (per-console)
HANDLE_CONSOLE_KEY = 0,
// Console ID
HANDLE_CONSOLE_ID = 1,
// NAND FS AES-128 key
HANDLE_FS_KEY = 2,
// NAND FS HMAC
HANDLE_FS_MAC = 3,
// Common key
HANDLE_COMMON_KEY = 4,
// PRNG seed
HANDLE_PRNG_KEY = 5,
// SD AES-128 key
HANDLE_SD_KEY = 6,
// boot2 version (writable)
HANDLE_BOOT2_VERSION = 7,
// Unknown
HANDLE_UNKNOWN_8 = 8,
// Unknown
HANDLE_UNKNOWN_9 = 9,
// Filesystem version (writable)
HANDLE_FS_VERSION = 10,
// New common key (aka Korean common key)
HANDLE_NEW_COMMON_KEY = 11,
};
enum ObjectType : u8
{
TYPE_SECRET_KEY = 0,
TYPE_PUBLIC_KEY = 1,
TYPE_DATA = 3,
};
enum ObjectSubType : u8
{
SUBTYPE_AES128 = 0,
SUBTYPE_MAC = 1,
SUBTYPE_ECC233 = 4,
SUBTYPE_DATA = 5,
SUBTYPE_VERSION = 6
};
// Create an object for use with the other functions that operate on objects.
ReturnCode CreateObject(Handle* handle, ObjectType type, ObjectSubType subtype, u32 pid);
// Delete an object. Built-in objects cannot be deleted.
ReturnCode DeleteObject(Handle handle, u32 pid);
// Import a secret, encrypted key into dest_handle, which will be decrypted using decrypt_handle.
ReturnCode ImportSecretKey(Handle dest_handle, Handle decrypt_handle, u8* iv,
const u8* encrypted_key, u32 pid);
// Import a public key.
ReturnCode ImportPublicKey(Handle dest_handle, const u8* public_key, u32 pid);
// Compute an AES key from an ECDH shared secret.
ReturnCode ComputeSharedKey(Handle dest_handle, Handle private_handle, Handle public_handle,
u32 pid);
// AES encrypt/decrypt.
ReturnCode Encrypt(Handle key_handle, u8* iv, const u8* input, size_t size, u8* output,
u32 pid) const;
ReturnCode Decrypt(Handle key_handle, u8* iv, const u8* input, size_t size, u8* output,
u32 pid) const;
// Ownership
ReturnCode GetOwnership(Handle handle, u32* owner) const;
ReturnCode SetOwnership(Handle handle, u32 owner, u32 pid);
void DoState(PointerWrap& p);
private:
struct KeyEntry
{
KeyEntry();
KeyEntry(ObjectType type_, ObjectSubType subtype_, std::vector<u8>&& data_, u32 owner_mask_);
void DoState(PointerWrap& p);
bool in_use = false;
ObjectType type;
ObjectSubType subtype;
std::vector<u8> data;
u32 owner_mask = 0;
};
// The Wii's IOSC is limited to 32 entries, including 12 built-in entries.
using KeyEntries = std::array<KeyEntry, 32>;
void LoadDefaultEntries();
KeyEntries::iterator FindFreeEntry();
Handle GetHandleFromIterator(KeyEntries::iterator iterator) const;
bool HasOwnership(Handle handle, u32 pid) const;
bool IsDefaultHandle(Handle handle) const;
ReturnCode DecryptEncrypt(Common::AES::Mode mode, Handle key_handle, u8* iv, const u8* input,
size_t size, u8* output, u32 pid) const;
KeyEntries m_key_entries;
};
} // namespace HLE
} // namespace IOS