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.
This commit is contained in:
Léo Lam 2017-05-01 19:20:50 +02:00
parent f8fb9e2d03
commit 08f6c31287
9 changed files with 63 additions and 107 deletions

View file

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

View file

@ -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<u8> 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<u8> 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<u8, 16> 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<u8, 16> iv{};
std::copy_n(ticket_begin + offsetof(Ticket, ticket_id), sizeof(Ticket::ticket_id), iv.begin());
const std::vector<u8> key =
Common::AES::Decrypt(shared_secret.data(), iv.data(),
&*ticket_begin + offsetof(Ticket, title_key), sizeof(Ticket::title_key));
std::array<u8, 16> 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

View file

@ -7,8 +7,6 @@
#include <cstring>
#include <vector>
#include <mbedtls/aes.h>
#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)

View file

@ -9,10 +9,10 @@
#include <utility>
#include <vector>
#include <mbedtls/aes.h>
#include <mbedtls/sha1.h>
#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<u8> 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<u8> 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<u8> 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<u8> 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;

View file

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

View file

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

View file

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

View file

@ -9,7 +9,6 @@
#include "Core/ec_wii.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
@ -180,19 +179,6 @@ const u8* EcWii::GetNGSig() const
return BootMiiKeysBin.ng_sig;
}
std::array<u8, 16> 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<u8, 20> sha1;
mbedtls_sha1(shared_secret.data(), shared_secret.size() / 2, sha1.data());
std::array<u8, 16> aes_key;
std::copy_n(sha1.cbegin(), aes_key.size(), aes_key.begin());
return aes_key;
}
void EcWii::InitDefaults()
{
memset(&BootMiiKeysBin, 0, sizeof(BootMiiKeysBin));

View file

@ -26,8 +26,6 @@
#include "Common/CommonTypes.h"
#include <array>
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<u8, 0x3c>;
std::array<u8, 16> GetSharedSecret(const ECCKey& peer_public_key) const;
private:
void InitDefaults();