IOS/ES: Handle personalised tickets properly
IOS unpersonalises device-specific ("personalised") tickets prior to storing them on the NAND.
This commit is contained in:
parent
71273c05a9
commit
9c31d6f5c5
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in New Issue