Skip to content

Commit

Permalink
IOS/ES: Handle personalised tickets properly
Browse files Browse the repository at this point in the history
IOS unpersonalises device-specific ("personalised") tickets prior to
storing them on the NAND.
  • Loading branch information
leoetlino committed Mar 11, 2017
1 parent 6c605cc commit ad60323
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Source/Core/Common/Crypto/ec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions Source/Core/Common/Crypto/ec.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
31 changes: 27 additions & 4 deletions Source/Core/Core/IOS/ES/ES.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,12 +422,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);
}

Expand Down
1 change: 1 addition & 0 deletions Source/Core/Core/IOS/ES/ES.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class ES : public Device
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,
Expand Down
34 changes: 34 additions & 0 deletions Source/Core/Core/IOS/ES/Formats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "Common/CommonTypes.h"
#include "Common/Crypto/AES.h"
#include "Common/Swap.h"
#include "Core/ec_wii.h"

namespace IOS
{
Expand Down Expand Up @@ -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));
Expand All @@ -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
11 changes: 9 additions & 2 deletions Source/Core/Core/IOS/ES/Formats.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -174,9 +176,14 @@ class TicketReader final
// 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;
};
Expand Down
14 changes: 14 additions & 0 deletions Source/Core/Core/ec_wii.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "Core/ec_wii.h"

#include <algorithm>
#include <cstdio>
#include <cstring>

Expand Down Expand Up @@ -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));
Expand Down
5 changes: 5 additions & 0 deletions Source/Core/Core/ec_wii.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -41,6 +43,9 @@ class EcWii
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();

Expand Down

0 comments on commit ad60323

Please sign in to comment.