diff --git a/Source/Core/Common/Crypto/ec.cpp b/Source/Core/Common/Crypto/ec.cpp index 93bbbad31d..9dca32878b 100644 --- a/Source/Core/Common/Crypto/ec.cpp +++ b/Source/Core/Common/Crypto/ec.cpp @@ -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; diff --git a/Source/Core/Common/Crypto/ec.h b/Source/Core/Common/Crypto/ec.h index b3f33cdf7e..3bf8b9904f 100644 --- a/Source/Core/Common/Crypto/ec.h +++ b/Source/Core/Common/Crypto/ec.h @@ -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); diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index aec8d49493..4f1e0d9a6f 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -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 ticket(request.in_vectors[0].size); - Memory::CopyFromEmu(ticket.data(), request.in_vectors[0].address, request.in_vectors[0].size); + std::vector 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); } diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index f7da4381d8..96428fad72 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -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, diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index e8ecffb6d5..484273d2d9 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -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 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 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 shared_secret = ec.GetSharedSecret(public_key); + + std::array iv{}; + std::copy_n(ticket_begin + offsetof(Ticket, ticket_id), sizeof(Ticket::ticket_id), iv.begin()); + + const std::vector 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 diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index 11466bce7c..ed0ec73caf 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -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 GetRawTicketView(u32 ticket_num) const; + u32 GetDeviceId() const; u64 GetTitleId() const; std::vector 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 m_bytes; }; diff --git a/Source/Core/Core/ec_wii.cpp b/Source/Core/Core/ec_wii.cpp index fc3b031e49..e726cf0699 100644 --- a/Source/Core/Core/ec_wii.cpp +++ b/Source/Core/Core/ec_wii.cpp @@ -9,6 +9,7 @@ #include "Core/ec_wii.h" +#include #include #include @@ -179,6 +180,19 @@ const u8* EcWii::GetNGSig() const return BootMiiKeysBin.ng_sig; } +std::array 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 sha1; + mbedtls_sha1(shared_secret.data(), shared_secret.size() / 2, sha1.data()); + + std::array aes_key; + std::copy_n(sha1.cbegin(), aes_key.size(), aes_key.begin()); + return aes_key; +} + void EcWii::InitDefaults() { memset(&BootMiiKeysBin, 0, sizeof(BootMiiKeysBin)); diff --git a/Source/Core/Core/ec_wii.h b/Source/Core/Core/ec_wii.h index 99b1bafeaf..a51e44a881 100644 --- a/Source/Core/Core/ec_wii.h +++ b/Source/Core/Core/ec_wii.h @@ -26,6 +26,8 @@ #include "Common/CommonTypes.h" +#include + 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; + std::array GetSharedSecret(const ECCKey& peer_public_key) const; + private: void InitDefaults();