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:
parent
f8fb9e2d03
commit
08f6c31287
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
if (ret == IPC_SUCCESS)
|
||||
std::copy(key.cbegin(), key.cend(), ticket_begin + offsetof(Ticket, title_key));
|
||||
return IOSC_OK;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct SharedContentMap::Entry
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in New Issue