IOS/ES: Handle personalised tickets properly

IOS unpersonalises device-specific ("personalised") tickets prior to
storing them on the NAND.
This commit is contained in:
Léo Lam 2017-03-08 12:40:50 +01:00
parent 71273c05a9
commit 9c31d6f5c5
8 changed files with 93 additions and 7 deletions

View File

@ -308,7 +308,7 @@ static void point_add(u8* r, const u8* p, const u8* q)
elt_add(ry, s, rx);
}
static void point_mul(u8* d, const u8* a, const u8* b) // a is bignum
void point_mul(u8* d, const u8* a, const u8* b) // a is bignum
{
u32 i;
u8 mask;

View File

@ -9,3 +9,5 @@
void generate_ecdsa(u8* R, u8* S, const u8* k, const u8* hash);
void ec_priv_to_pub(const u8* k, u8* Q);
void point_mul(u8* d, const u8* a, const u8* b);

View File

@ -427,12 +427,35 @@ IPCCommandResult ES::AddTicket(const IOCtlVRequest& request)
if (!request.HasNumberOfValidVectors(3, 0))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
INFO_LOG(IOS_ES, "IOCTL_ES_ADDTICKET");
std::vector<u8> ticket(request.in_vectors[0].size);
Memory::CopyFromEmu(ticket.data(), request.in_vectors[0].address, request.in_vectors[0].size);
std::vector<u8> bytes(request.in_vectors[0].size);
Memory::CopyFromEmu(bytes.data(), request.in_vectors[0].address, request.in_vectors[0].size);
DiscIO::AddTicket(IOS::ES::TicketReader{std::move(ticket)});
IOS::ES::TicketReader ticket{std::move(bytes)};
if (!ticket.IsValid())
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
const u32 ticket_device_id = ticket.GetDeviceId();
const u32 device_id = EcWii::GetInstance().GetNGID();
if (ticket_device_id != 0)
{
if (device_id != ticket_device_id)
{
WARN_LOG(IOS_ES, "Device ID mismatch: ticket %08x, device %08x", ticket_device_id, device_id);
return GetDefaultReply(ES_DEVICE_ID_MISMATCH);
}
const s32 ret = ticket.Unpersonalise();
if (ret < 0)
{
ERROR_LOG(IOS_ES, "AddTicket: Failed to unpersonalise ticket for %016" PRIx64 " (ret = %d)",
ticket.GetTitleId(), ret);
return GetDefaultReply(ret);
}
}
if (!DiscIO::AddTicket(ticket))
return GetDefaultReply(ES_WRITE_FAILURE);
INFO_LOG(IOS_ES, "AddTicket: Imported ticket for title %016" PRIx64, ticket.GetTitleId());
return GetDefaultReply(IPC_SUCCESS);
}

View File

@ -119,6 +119,7 @@ private:
ES_READ_LESS_DATA_THAN_EXPECTED = -1009,
ES_WRITE_FAILURE = -1010,
ES_PARAMETER_SIZE_OR_ALIGNMENT = -1017,
ES_DEVICE_ID_MISMATCH = -1020,
ES_HASH_DOESNT_MATCH = -1022,
ES_MEM_ALLOC_FAILED = -1024,
ES_INCORRECT_ACCESS_RIGHT = -1026,

View File

@ -14,6 +14,7 @@
#include "Common/CommonTypes.h"
#include "Common/Crypto/AES.h"
#include "Common/Swap.h"
#include "Core/ec_wii.h"
namespace IOS
{
@ -270,6 +271,11 @@ std::vector<u8> TicketReader::GetRawTicketView(u32 ticket_num) const
return view;
}
u32 TicketReader::GetDeviceId() const
{
return Common::swap32(m_bytes.data() + GetOffset() + offsetof(Ticket, device_id));
}
u64 TicketReader::GetTitleId() const
{
return Common::swap64(m_bytes.data() + GetOffset() + offsetof(Ticket, title_id));
@ -284,5 +290,33 @@ std::vector<u8> TicketReader::GetTitleKey() const
return Common::AES::Decrypt(common_key, iv, &m_bytes[GetOffset() + offsetof(Ticket, title_key)],
16);
}
constexpr s32 IOSC_OK = 0;
s32 TicketReader::Unpersonalise()
{
const auto ticket_begin = m_bytes.begin() + GetOffset();
// 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.
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());
const EcWii& ec = EcWii::GetInstance();
const std::array<u8, 16> shared_secret = ec.GetSharedSecret(public_key);
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));
// 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;
}
} // namespace ES
} // namespace IOS

View File

@ -97,8 +97,10 @@ static_assert(sizeof(TicketView) == 0xd8, "TicketView has the wrong size");
struct Ticket
{
u8 signature_issuer[0x40];
u8 ecdh_key[0x3c];
u8 unknown[0x03];
u8 server_public_key[0x3c];
u8 version;
u8 ca_crl_version;
u8 signer_crl_version;
u8 title_key[0x10];
u64 ticket_id;
u32 device_id;
@ -174,9 +176,14 @@ public:
// more than just one ticket and generate ticket views for them, so we implement it too.
std::vector<u8> GetRawTicketView(u32 ticket_num) const;
u32 GetDeviceId() const;
u64 GetTitleId() const;
std::vector<u8> GetTitleKey() const;
// Decrypts the title key field for a "personalised" ticket -- one that is device-specific
// and has a title key that must be decrypted first.
s32 Unpersonalise();
private:
std::vector<u8> m_bytes;
};

View File

@ -9,6 +9,7 @@
#include "Core/ec_wii.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
@ -179,6 +180,19 @@ 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,6 +26,8 @@
#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);
@ -41,6 +43,9 @@ 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();