From 5275fd2789459c1c633b2436ddb0d352018650d7 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 10:21:44 -0400 Subject: [PATCH 01/15] key_manager: Add equality operator for RSAKeyPair --- src/core/crypto/key_manager.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 22f268c655..589b256963 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -43,6 +43,13 @@ struct RSAKeyPair { std::array exponent; }; +template +bool operator==(const RSAKeyPair& lhs, + const RSAKeyPair& rhs) { + return std::tie(lhs.encryption_key, lhs.decryption_key, lhs.modulus, lhs.exponent) == + std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent); +} + enum class KeyCategory : u8 { Standard, Title, From e35fac205406c6d485bd755c4260a69f312eeaf6 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 10:22:04 -0400 Subject: [PATCH 02/15] key_manager: Add accessors/helpers for ticket management --- src/core/crypto/key_manager.cpp | 100 +++++++++++++++++++++++++++----- src/core/crypto/key_manager.h | 14 +++++ 2 files changed, 100 insertions(+), 14 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 6dd6333639..ef139902d1 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -135,6 +135,28 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { } } +RSAKeyPair<2048> KeyManager::GetETicketRSAKey() { + if (eticket_extended_kek == std::array{} || !HasKey(S128KeyType::ETicketRSAKek)) + return {}; + + const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); + + std::vector extended_iv(0x10); + std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); + std::array extended_dec{}; + AESCipher rsa_1(eticket_final, Mode::CTR); + rsa_1.SetIV(extended_iv); + rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, + extended_dec.data(), Op::Decrypt); + + RSAKeyPair<2048> rsa_key{}; + std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); + std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); + std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); + + return rsa_key; +} + Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { AESCipher mac_cipher(keyblob_key, Mode::ECB); Key128 mac_key{}; @@ -450,6 +472,8 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); + } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { + eticket_extended_kek = Common::HexStringToArray<576>(out[1]); } else { for (const auto& kv : KEYS_VARIABLE_LENGTH) { if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) @@ -862,20 +886,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { // Titlekeys data.DecryptProdInfo(GetBISKey(0)); - const auto eticket_extended_kek = data.GetETicketExtendedKek(); + eticket_extended_kek = data.GetETicketExtendedKek(); + WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); + PopulateTickets(); +} - std::vector extended_iv(0x10); - std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); - std::array extended_dec{}; - AESCipher rsa_1(eticket_final, Mode::CTR); - rsa_1.SetIV(extended_iv); - rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, - extended_dec.data(), Op::Decrypt); +void KeyManager::PopulateTickets() { + const auto rsa_key = GetETicketRSAKey(); - RSAKeyPair<2048> rsa_key{}; - std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); - std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); - std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); + if (rsa_key == RSAKeyPair<2048>{}) + return; + + if (!common_tickets.empty() && !personal_tickets.empty()) + return; const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/80000000000000e1", @@ -886,15 +909,24 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { const auto blob2 = GetTicketblob(save2); auto res = GetTicketblob(save1); + const auto idx = res.size(); res.insert(res.end(), blob2.begin(), blob2.end()); - for (const auto& raw : res) { - const auto pair = ParseTicket(raw, rsa_key); + for (std::size_t i = 0; i < res.size(); ++i) { + const auto common = i < idx; + const auto pair = ParseTicket(res[i], rsa_key); if (!pair) continue; const auto& [rid, key] = *pair; u128 rights_id; std::memcpy(rights_id.data(), rid.data(), rid.size()); + + if (common) { + common_tickets[rights_id] = res[i]; + } else { + personal_tickets[rights_id] = res[i]; + } + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); } } @@ -997,6 +1029,46 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { DeriveBase(); } +const std::map& KeyManager::GetCommonTickets() const { + return common_tickets; +} + +const std::map& KeyManager::GetPersonalizedTickets() const { + return personal_tickets; +} + +bool KeyManager::AddTicketCommon(TicketRaw raw) { + const auto rsa_key = GetETicketRSAKey(); + if (rsa_key == RSAKeyPair<2048>{}) + return false; + + const auto pair = ParseTicket(raw, rsa_key); + if (!pair) + return false; + const auto& [rid, key] = *pair; + u128 rights_id; + std::memcpy(rights_id.data(), rid.data(), rid.size()); + common_tickets[rights_id] = raw; + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + return true; +} + +bool KeyManager::AddTicketPersonalized(TicketRaw raw) { + const auto rsa_key = GetETicketRSAKey(); + if (rsa_key == RSAKeyPair<2048>{}) + return false; + + const auto pair = ParseTicket(raw, rsa_key); + if (!pair) + return false; + const auto& [rid, key] = *pair; + u128 rights_id; + std::memcpy(rights_id.data(), rid.data(), rid.size()); + common_tickets[rights_id] = raw; + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + return true; +} + const boost::container::flat_map> KeyManager::s128_file_id = { {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, {"eticket_rsa_kek_source", diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 589b256963..8a67b172d3 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -165,15 +165,27 @@ public: bool BaseDeriveNecessary() const; void DeriveBase(); void DeriveETicket(PartitionDataManager& data); + void PopulateTickets(); void PopulateFromPartitionData(PartitionDataManager& data); + const std::map& GetCommonTickets() const; + const std::map& GetPersonalizedTickets() const; + + bool AddTicketCommon(TicketRaw raw); + bool AddTicketPersonalized(TicketRaw raw); + private: std::map, Key128> s128_keys; std::map, Key256> s256_keys; + // Map from rights ID to ticket + std::map common_tickets; + std::map personal_tickets; + std::array, 0x20> encrypted_keyblobs{}; std::array, 0x20> keyblobs{}; + std::array eticket_extended_kek{}; bool dev_mode; void LoadFromFile(const std::string& filename, bool is_title_keys); @@ -185,6 +197,8 @@ private: void DeriveGeneralPurposeKeys(std::size_t crypto_revision); + RSAKeyPair<2048> GetETicketRSAKey(); + void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); From f15f73a555f0a4eeda5eb675b1a30aadf695e74f Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 13:59:41 -0400 Subject: [PATCH 03/15] es: Implement ETicket ImportTicket (1) Takes a ticket and certificate and installs it to the KeyManager. --- src/core/hle/service/es/es.cpp | 46 +++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 6701cb9135..787927be08 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -2,16 +2,21 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/crypto/key_manager.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/service/service.h" namespace Service::ES { +constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2}; +constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3}; + class ETicket final : public ServiceFramework { public: explicit ETicket() : ServiceFramework{"es"} { // clang-format off static const FunctionInfo functions[] = { - {1, nullptr, "ImportTicket"}, + {1, &ETicket::ImportTicket, "ImportTicket"}, {2, nullptr, "ImportTicketCertificateSet"}, {3, nullptr, "DeleteTicket"}, {4, nullptr, "DeletePersonalizedTicket"}, @@ -52,6 +57,45 @@ public: // clang-format on RegisterHandlers(functions); } + +private: + bool CheckRightsId(Kernel::HLERequestContext& ctx, const u128& rights_id) { + if (rights_id == u128{}) { + LOG_ERROR(Service_ETicket, "The rights ID was invalid!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_RIGHTS_ID); + return false; + } + + return true; + } + + void ImportTicket(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto ticket = ctx.ReadBuffer(); + const auto cert = ctx.ReadBuffer(1); + + if (ticket.size() < sizeof(Core::Crypto::TicketRaw)) { + LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + Core::Crypto::TicketRaw raw; + std::memcpy(raw.data(), ticket.data(), sizeof(Core::Crypto::TicketRaw)); + + if (!keys.AddTicketPersonalized(raw)) { + LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + }; void InstallInterfaces(SM::ServiceManager& service_manager) { From 475a7a4446b169b46d4fb34f3b022f7b369e5bf9 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 14:00:39 -0400 Subject: [PATCH 04/15] es: Implement ETicket GetTitleKey (8) Takes a rights ID as input and returns the associated title key, if it exists. --- src/core/hle/service/es/es.cpp | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 787927be08..65dfaa2a00 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -23,7 +23,7 @@ public: {5, nullptr, "DeleteAllCommonTicket"}, {6, nullptr, "DeleteAllPersonalizedTicket"}, {7, nullptr, "DeleteAllPersonalizedTicketEx"}, - {8, nullptr, "GetTitleKey"}, + {8, &ETicket::GetTitleKey, "GetTitleKey"}, {9, nullptr, "CountCommonTicket"}, {10, nullptr, "CountPersonalizedTicket"}, {11, nullptr, "ListCommonTicket"}, @@ -96,6 +96,32 @@ private: rb.Push(RESULT_SUCCESS); } + void GetTitleKey(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto key = + keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); + + if (key == Core::Crypto::Key128{}) { + LOG_ERROR(Service_ETicket, + "The titlekey doesn't exist in the KeyManager or the rights ID was invalid!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_RIGHTS_ID); + return; + } + + ctx.WriteBuffer(key.data(), key.size()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + }; void InstallInterfaces(SM::ServiceManager& service_manager) { From 71bc2182c2e27c7dc635bd527325ebcde1f76ed0 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 14:02:27 -0400 Subject: [PATCH 05/15] es: Implement ETicket CountCommonTicket (9) Returns the number of common (non-console-unique) tickets in the KeyManager. --- src/core/hle/service/es/es.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 65dfaa2a00..1c6aef029f 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -24,7 +24,7 @@ public: {6, nullptr, "DeleteAllPersonalizedTicket"}, {7, nullptr, "DeleteAllPersonalizedTicketEx"}, {8, &ETicket::GetTitleKey, "GetTitleKey"}, - {9, nullptr, "CountCommonTicket"}, + {9, &ETicket::CountCommonTicket, "CountCommonTicket"}, {10, nullptr, "CountPersonalizedTicket"}, {11, nullptr, "ListCommonTicket"}, {12, nullptr, "ListPersonalizedTicket"}, @@ -122,6 +122,17 @@ private: rb.Push(RESULT_SUCCESS); } + void CountCommonTicket(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ETicket, "called"); + + keys.PopulateTickets(); + const auto count = keys.GetCommonTickets().size(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(count); + } + }; void InstallInterfaces(SM::ServiceManager& service_manager) { From 11f45e60159cdd215158e2c60ac08ed4e5a07439 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 14:03:08 -0400 Subject: [PATCH 06/15] es: Implement ETicket CountPersonalizedTicket (10) Returns the number of personalized (console/user-unique) tickets in the KeyManager. --- src/core/hle/service/es/es.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 1c6aef029f..9adb39ac35 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -25,7 +25,7 @@ public: {7, nullptr, "DeleteAllPersonalizedTicketEx"}, {8, &ETicket::GetTitleKey, "GetTitleKey"}, {9, &ETicket::CountCommonTicket, "CountCommonTicket"}, - {10, nullptr, "CountPersonalizedTicket"}, + {10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"}, {11, nullptr, "ListCommonTicket"}, {12, nullptr, "ListPersonalizedTicket"}, {13, nullptr, "ListMissingPersonalizedTicket"}, @@ -133,6 +133,17 @@ private: rb.Push(count); } + void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ETicket, "called"); + + keys.PopulateTickets(); + const auto count = keys.GetPersonalizedTickets().size(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(count); + } + }; void InstallInterfaces(SM::ServiceManager& service_manager) { From 5d6bf75296984eb2d4c7e1486daeb306a5fefa32 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 14:04:17 -0400 Subject: [PATCH 07/15] es: Implement ETicket ListCommonTicket (11) Returns an application specified count of entries of common tickets, starting at offset 0. --- src/core/hle/service/es/es.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 9adb39ac35..9a481f41f7 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -26,7 +26,7 @@ public: {8, &ETicket::GetTitleKey, "GetTitleKey"}, {9, &ETicket::CountCommonTicket, "CountCommonTicket"}, {10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"}, - {11, nullptr, "ListCommonTicket"}, + {11, &ETicket::ListCommonTicket, "ListCommonTicket"}, {12, nullptr, "ListPersonalizedTicket"}, {13, nullptr, "ListMissingPersonalizedTicket"}, {14, nullptr, "GetCommonTicketSize"}, @@ -144,6 +144,29 @@ private: rb.Push(count); } + void ListCommonTicket(Kernel::HLERequestContext& ctx) { + u32 out_entries; + if (keys.GetCommonTickets().empty()) + out_entries = 0; + else + out_entries = ctx.GetWriteBufferSize() / sizeof(u128); + + LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries); + + keys.PopulateTickets(); + const auto tickets = keys.GetCommonTickets(); + std::vector ids; + std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids), + [](const auto& pair) { return pair.first; }); + + out_entries = std::min(ids.size(), out_entries); + ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128)); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(out_entries); + } + }; void InstallInterfaces(SM::ServiceManager& service_manager) { From 669a21babb89c670746ed0a3ba89543a70f8e05e Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 14:05:12 -0400 Subject: [PATCH 08/15] es: Implement ETicket ListPersonalizedTicket (12) Returns an application-specific number of entries of personal tickets, starting at offset 0. --- src/core/hle/service/es/es.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 9a481f41f7..d136566fcf 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -27,7 +27,7 @@ public: {9, &ETicket::CountCommonTicket, "CountCommonTicket"}, {10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"}, {11, &ETicket::ListCommonTicket, "ListCommonTicket"}, - {12, nullptr, "ListPersonalizedTicket"}, + {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"}, {13, nullptr, "ListMissingPersonalizedTicket"}, {14, nullptr, "GetCommonTicketSize"}, {15, nullptr, "GetPersonalizedTicketSize"}, @@ -167,6 +167,29 @@ private: rb.Push(out_entries); } + void ListPersonalizedTicket(Kernel::HLERequestContext& ctx) { + u32 out_entries; + if (keys.GetPersonalizedTickets().empty()) + out_entries = 0; + else + out_entries = ctx.GetWriteBufferSize() / sizeof(u128); + + LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries); + + keys.PopulateTickets(); + const auto tickets = keys.GetPersonalizedTickets(); + std::vector ids; + std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids), + [](const auto& pair) { return pair.first; }); + + out_entries = std::min(ids.size(), out_entries); + ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128)); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(out_entries); + } + }; void InstallInterfaces(SM::ServiceManager& service_manager) { From 35b617b57ffb46d33886304b82ae80b40d80c042 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 14:06:17 -0400 Subject: [PATCH 09/15] es: Implement ETicket GetCommonTicketSize (14) Returns the size of the buffer needed to hold the common ticket associated with the rights ID. --- src/core/hle/service/es/es.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index d136566fcf..0125b3ba9b 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -29,7 +29,7 @@ public: {11, &ETicket::ListCommonTicket, "ListCommonTicket"}, {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"}, {13, nullptr, "ListMissingPersonalizedTicket"}, - {14, nullptr, "GetCommonTicketSize"}, + {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"}, {15, nullptr, "GetPersonalizedTicketSize"}, {16, nullptr, "GetCommonTicketData"}, {17, nullptr, "GetPersonalizedTicketData"}, @@ -190,6 +190,22 @@ private: rb.Push(out_entries); } + void GetCommonTicketSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetCommonTickets().at(rights_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(ticket.size()); + } + }; void InstallInterfaces(SM::ServiceManager& service_manager) { From 44b0c19f6a0a9491028800ca0a10dc74c954b258 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 14:07:00 -0400 Subject: [PATCH 10/15] es: Implement ETicket GetPersonalizedTicketSize (15) Returns the size of the buffer needed to hold the personal ticket associated with the rights ID. --- src/core/hle/service/es/es.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 0125b3ba9b..e18f27e7ad 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -30,7 +30,7 @@ public: {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"}, {13, nullptr, "ListMissingPersonalizedTicket"}, {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"}, - {15, nullptr, "GetPersonalizedTicketSize"}, + {15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"}, {16, nullptr, "GetCommonTicketData"}, {17, nullptr, "GetPersonalizedTicketData"}, {18, nullptr, "OwnTicket"}, @@ -206,6 +206,22 @@ private: rb.Push(ticket.size()); } + void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetPersonalizedTickets().at(rights_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(ticket.size()); + } + }; void InstallInterfaces(SM::ServiceManager& service_manager) { From c6a32dc077f3d55421898294ada0a5fe93400b9e Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 14:07:49 -0400 Subject: [PATCH 11/15] es: Implement ETicket GetCommonTicketData (16) Copies the raw common ticket data for the specified rights ID into the buffer provided. --- src/core/hle/service/es/es.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index e18f27e7ad..8a29453d78 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -31,7 +31,7 @@ public: {13, nullptr, "ListMissingPersonalizedTicket"}, {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"}, {15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"}, - {16, nullptr, "GetCommonTicketData"}, + {16, &ETicket::GetCommonTicketData, "GetCommonTicketData"}, {17, nullptr, "GetPersonalizedTicketData"}, {18, nullptr, "OwnTicket"}, {19, nullptr, "GetTicketInfo"}, @@ -222,6 +222,25 @@ private: rb.Push(ticket.size()); } + void GetCommonTicketData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetCommonTickets().at(rights_id); + + const auto write_size = std::min(ticket.size(), ctx.GetWriteBufferSize()); + ctx.WriteBuffer(ticket.data(), write_size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(write_size); + } + }; void InstallInterfaces(SM::ServiceManager& service_manager) { From b294b13584b308cdc7819cf9b4b3859ee3b9b3c2 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 10 Apr 2019 14:08:19 -0400 Subject: [PATCH 12/15] es: Implement ETicket GetPersonalizedTicketData (17) Copies the raw personal ticket data into the buffer provided. --- src/core/hle/service/es/es.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 8a29453d78..d17fb778c9 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -32,7 +32,7 @@ public: {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"}, {15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"}, {16, &ETicket::GetCommonTicketData, "GetCommonTicketData"}, - {17, nullptr, "GetPersonalizedTicketData"}, + {17, &ETicket::GetPersonalizedTicketData, "GetPersonalizedTicketData"}, {18, nullptr, "OwnTicket"}, {19, nullptr, "GetTicketInfo"}, {20, nullptr, "ListLightTicketInfo"}, @@ -241,6 +241,26 @@ private: rb.Push(write_size); } + void GetPersonalizedTicketData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetPersonalizedTickets().at(rights_id); + + const auto write_size = std::min(ticket.size(), ctx.GetWriteBufferSize()); + ctx.WriteBuffer(ticket.data(), write_size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(write_size); + } + + Core::Crypto::KeyManager keys; }; void InstallInterfaces(SM::ServiceManager& service_manager) { From f8718ae779bbdc6a3f514b5ce141515baa97e14f Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Tue, 16 Apr 2019 09:12:04 -0400 Subject: [PATCH 13/15] key_manager: Add structure for Ticket parsing --- src/core/crypto/key_manager.cpp | 124 ++++++++++++++++++++++++++------ src/core/crypto/key_manager.h | 96 +++++++++++++++++++++---- src/core/hle/service/es/es.cpp | 18 ++--- 3 files changed, 194 insertions(+), 44 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index ef139902d1..558790a495 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -37,6 +37,7 @@ namespace Core::Crypto { constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; +constexpr u64 FULL_TICKET_SIZE = 0x400; using namespace Common; @@ -55,6 +56,78 @@ const std::map, std::string> KEYS_VARIABLE_LENGTH{ {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, }; +u64 GetSignatureTypeDataSize(SignatureType type) { + switch (type) { + case SignatureType::RSA_4096_SHA1: + case SignatureType::RSA_4096_SHA256: + return 0x200; + case SignatureType::RSA_2048_SHA1: + case SignatureType::RSA_2048_SHA256: + return 0x100; + case SignatureType::ECDSA_SHA1: + case SignatureType::ECDSA_SHA256: + return 0x3C; + } + UNREACHABLE(); +} + +u64 GetSignatureTypePaddingSize(SignatureType type) { + switch (type) { + case SignatureType::RSA_4096_SHA1: + case SignatureType::RSA_4096_SHA256: + case SignatureType::RSA_2048_SHA1: + case SignatureType::RSA_2048_SHA256: + return 0x3C; + case SignatureType::ECDSA_SHA1: + case SignatureType::ECDSA_SHA256: + return 0x40; + } + UNREACHABLE(); +} + +TicketData& Ticket::GetData() { + switch (sig_type) { + case SignatureType::RSA_4096_SHA1: + case SignatureType::RSA_4096_SHA256: + return rsa_4096.data; + case SignatureType::RSA_2048_SHA1: + case SignatureType::RSA_2048_SHA256: + return rsa_2048.data; + case SignatureType::ECDSA_SHA1: + case SignatureType::ECDSA_SHA256: + return ecdsa.data; + } + UNREACHABLE(); +} + +const TicketData& Ticket::GetData() const { + switch (sig_type) { + case SignatureType::RSA_4096_SHA1: + case SignatureType::RSA_4096_SHA256: + return rsa_4096.data; + case SignatureType::RSA_2048_SHA1: + case SignatureType::RSA_2048_SHA256: + return rsa_2048.data; + case SignatureType::ECDSA_SHA1: + case SignatureType::ECDSA_SHA256: + return ecdsa.data; + } + UNREACHABLE(); +} + +u64 Ticket::GetSize() const { + return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) + + GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData); +} + +Ticket Ticket::SynthesizeCommon(Key128 title_key, std::array rights_id) { + Ticket out{}; + out.sig_type = SignatureType::RSA_2048_SHA256; + out.GetData().rights_id = rights_id; + out.GetData().title_key_common = title_key; + return out; +} + Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { Key128 out{}; @@ -259,7 +332,7 @@ Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, KeyManager& ke return Loader::ResultStatus::Success; } -std::vector GetTicketblob(const FileUtil::IOFile& ticket_save) { +std::vector GetTicketblob(const FileUtil::IOFile& ticket_save) { if (!ticket_save.IsOpen()) return {}; @@ -268,14 +341,14 @@ std::vector GetTicketblob(const FileUtil::IOFile& ticket_save) { return {}; } - std::vector out; + std::vector out; for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && buffer[offset + 3] == 0x0) { out.emplace_back(); auto& next = out.back(); - std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw)); - offset += next.size(); + std::memcpy(&next, buffer.data() + offset, sizeof(Ticket)); + offset += FULL_TICKET_SIZE; } } @@ -327,29 +400,25 @@ static std::optional FindTicketOffset(const std::array& data) { return offset; } -std::optional> ParseTicket(const TicketRaw& ticket, +std::optional> ParseTicket(const Ticket& ticket, const RSAKeyPair<2048>& key) { - u32 cert_authority; - std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority)); - if (cert_authority == 0) + const auto issuer = ticket.GetData().issuer; + if (issuer == std::array{}) return {}; - if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) { + if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority {:08X}.", - cert_authority); + issuer); } - Key128 rights_id; - std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128)); + Key128 rights_id = ticket.GetData().rights_id; if (rights_id == Key128{}) return {}; - Key128 key_temp{}; - - if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) { - std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size()); - return std::make_pair(rights_id, key_temp); + if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), + ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { + return std::make_pair(rights_id, ticket.GetData().title_key_common); } mbedtls_mpi D; // RSA Private Exponent @@ -364,7 +433,7 @@ std::optional> ParseTicket(const TicketRaw& ticket, mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); - mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100); + mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); @@ -388,6 +457,7 @@ std::optional> ParseTicket(const TicketRaw& ticket, return {}; ASSERT(*offset > 0); + Key128 key_temp{}; std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); return std::make_pair(rights_id, key_temp); @@ -411,6 +481,16 @@ KeyManager::KeyManager() { AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true); AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false); AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false); + + for (const auto& key : s128_keys) { + if (key.first.type == S128KeyType::Titlekey) { + u128 rights_id{key.first.field1, key.first.field2}; + Key128 rights_id_2; + std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); + const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); + common_tickets.insert_or_assign(rights_id, ticket); + } + } } static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { @@ -1029,15 +1109,15 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { DeriveBase(); } -const std::map& KeyManager::GetCommonTickets() const { +const std::map& KeyManager::GetCommonTickets() const { return common_tickets; } -const std::map& KeyManager::GetPersonalizedTickets() const { +const std::map& KeyManager::GetPersonalizedTickets() const { return personal_tickets; } -bool KeyManager::AddTicketCommon(TicketRaw raw) { +bool KeyManager::AddTicketCommon(Ticket raw) { const auto rsa_key = GetETicketRSAKey(); if (rsa_key == RSAKeyPair<2048>{}) return false; @@ -1053,7 +1133,7 @@ bool KeyManager::AddTicketCommon(TicketRaw raw) { return true; } -bool KeyManager::AddTicketPersonalized(TicketRaw raw) { +bool KeyManager::AddTicketPersonalized(Ticket raw) { const auto rsa_key = GetETicketRSAKey(); if (rsa_key == RSAKeyPair<2048>{}) return false; diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 8a67b172d3..ff6bd08e16 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -11,6 +11,7 @@ #include #include +#include "common/common_funcs.h" #include "common/common_types.h" #include "core/crypto/partition_data_manager.h" #include "core/file_sys/vfs_types.h" @@ -30,7 +31,76 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; using Key128 = std::array; using Key256 = std::array; using SHA256Hash = std::array; -using TicketRaw = std::array; + +enum class SignatureType { + RSA_4096_SHA1 = 0x10000, + RSA_2048_SHA1 = 0x10001, + ECDSA_SHA1 = 0x10002, + RSA_4096_SHA256 = 0x10003, + RSA_2048_SHA256 = 0x10004, + ECDSA_SHA256 = 0x10005, +}; + +u64 GetSignatureTypeDataSize(SignatureType type); +u64 GetSignatureTypePaddingSize(SignatureType type); + +enum class TitleKeyType : u8 { + Common = 0, + Personalized = 1, +}; + +struct TicketData { + std::array issuer; + union { + std::array title_key_block; + + struct { + Key128 title_key_common; + std::array title_key_common_pad; + }; + }; + + INSERT_PADDING_BYTES(0x1); + TitleKeyType type; + INSERT_PADDING_BYTES(0x3); + u8 revision; + INSERT_PADDING_BYTES(0xA); + u64 ticket_id; + u64 device_id; + std::array rights_id; + u32 account_id; + INSERT_PADDING_BYTES(0x14C); +}; +static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size."); + +struct Ticket { + SignatureType sig_type; + union { + struct { + std::array sig_data; + INSERT_PADDING_BYTES(0x3C); + TicketData data; + } rsa_4096; + + struct { + std::array sig_data; + INSERT_PADDING_BYTES(0x3C); + TicketData data; + } rsa_2048; + + struct { + std::array sig_data; + INSERT_PADDING_BYTES(0x40); + TicketData data; + } ecdsa; + }; + + TicketData& GetData(); + const TicketData& GetData() const; + u64 GetSize() const; + + static Ticket SynthesizeCommon(Key128 title_key, std::array rights_id); +}; static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); @@ -158,8 +228,8 @@ public: static bool KeyFileExists(bool title); - // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save - // 8*43 and the private file to exist. + // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system + // save 8*43 and the private file to exist. void DeriveSDSeedLazy(); bool BaseDeriveNecessary() const; @@ -169,19 +239,19 @@ public: void PopulateFromPartitionData(PartitionDataManager& data); - const std::map& GetCommonTickets() const; - const std::map& GetPersonalizedTickets() const; + const std::map& GetCommonTickets() const; + const std::map& GetPersonalizedTickets() const; - bool AddTicketCommon(TicketRaw raw); - bool AddTicketPersonalized(TicketRaw raw); + bool AddTicketCommon(Ticket raw); + bool AddTicketPersonalized(Ticket raw); private: std::map, Key128> s128_keys; std::map, Key256> s256_keys; // Map from rights ID to ticket - std::map common_tickets; - std::map personal_tickets; + std::map common_tickets; + std::map personal_tickets; std::array, 0x20> encrypted_keyblobs{}; std::array, 0x20> keyblobs{}; @@ -216,11 +286,11 @@ std::array DecryptKeyblob(const std::array& encrypted_keyblo std::optional DeriveSDSeed(); Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, KeyManager& keys); -std::vector GetTicketblob(const FileUtil::IOFile& ticket_save); +std::vector GetTicketblob(const FileUtil::IOFile& ticket_save); -// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset -// 0x140-0x144 is zero) -std::optional> ParseTicket(const TicketRaw& ticket, +// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority +// (offset 0x140-0x144 is zero) +std::optional> ParseTicket(const Ticket& ticket, const RSAKeyPair<2048>& eticket_extended_key); } // namespace Core::Crypto diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index d17fb778c9..7e01f88b90 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -75,15 +75,15 @@ private: const auto ticket = ctx.ReadBuffer(); const auto cert = ctx.ReadBuffer(1); - if (ticket.size() < sizeof(Core::Crypto::TicketRaw)) { + if (ticket.size() < sizeof(Core::Crypto::Ticket)) { LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERROR_INVALID_ARGUMENT); return; } - Core::Crypto::TicketRaw raw; - std::memcpy(raw.data(), ticket.data(), sizeof(Core::Crypto::TicketRaw)); + Core::Crypto::Ticket raw{}; + std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket)); if (!keys.AddTicketPersonalized(raw)) { LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); @@ -203,7 +203,7 @@ private: IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(ticket.size()); + rb.Push(ticket.GetSize()); } void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) { @@ -219,7 +219,7 @@ private: IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(ticket.size()); + rb.Push(ticket.GetSize()); } void GetCommonTicketData(Kernel::HLERequestContext& ctx) { @@ -233,8 +233,8 @@ private: const auto ticket = keys.GetCommonTickets().at(rights_id); - const auto write_size = std::min(ticket.size(), ctx.GetWriteBufferSize()); - ctx.WriteBuffer(ticket.data(), write_size); + const auto write_size = std::min(ticket.GetSize(), ctx.GetWriteBufferSize()); + ctx.WriteBuffer(&ticket, write_size); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); @@ -252,8 +252,8 @@ private: const auto ticket = keys.GetPersonalizedTickets().at(rights_id); - const auto write_size = std::min(ticket.size(), ctx.GetWriteBufferSize()); - ctx.WriteBuffer(ticket.data(), write_size); + const auto write_size = std::min(ticket.GetSize(), ctx.GetWriteBufferSize()); + ctx.WriteBuffer(&ticket, write_size); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); From d9ef20e5a53166fe3ecdca5ed225232eb7ad2f64 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 17 Apr 2019 11:29:21 -0400 Subject: [PATCH 14/15] es: Populate/synthesize tickets on construction --- src/core/crypto/key_manager.cpp | 26 +++++++++++++------------- src/core/crypto/key_manager.h | 1 + src/core/hle/service/es/es.cpp | 5 +++-- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 558790a495..3c51e3dc21 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -406,9 +406,7 @@ std::optional> ParseTicket(const Ticket& ticket, if (issuer == std::array{}) return {}; if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { - LOG_INFO(Crypto, - "Attempting to parse ticket with non-standard certificate authority {:08X}.", - issuer); + LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); } Key128 rights_id = ticket.GetData().rights_id; @@ -481,16 +479,6 @@ KeyManager::KeyManager() { AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true); AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false); AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false); - - for (const auto& key : s128_keys) { - if (key.first.type == S128KeyType::Titlekey) { - u128 rights_id{key.first.field1, key.first.field2}; - Key128 rights_id_2; - std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); - const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); - common_tickets.insert_or_assign(rights_id, ticket); - } - } } static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { @@ -1011,6 +999,18 @@ void KeyManager::PopulateTickets() { } } +void KeyManager::SynthesizeTickets() { + for (const auto& key : s128_keys) { + if (key.first.type == S128KeyType::Titlekey) { + u128 rights_id{key.first.field1, key.first.field2}; + Key128 rights_id_2; + std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); + const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); + common_tickets.insert_or_assign(rights_id, ticket); + } + } +} + void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { if (key == Key128{}) return; diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index ff6bd08e16..d4e89d35c3 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -236,6 +236,7 @@ public: void DeriveBase(); void DeriveETicket(PartitionDataManager& data); void PopulateTickets(); + void SynthesizeTickets(); void PopulateFromPartitionData(PartitionDataManager& data); diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 7e01f88b90..92fa2bef88 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -56,6 +56,9 @@ public: }; // clang-format on RegisterHandlers(functions); + + keys.PopulateTickets(); + keys.SynthesizeTickets(); } private: @@ -125,7 +128,6 @@ private: void CountCommonTicket(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ETicket, "called"); - keys.PopulateTickets(); const auto count = keys.GetCommonTickets().size(); IPC::ResponseBuilder rb{ctx, 3}; @@ -136,7 +138,6 @@ private: void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ETicket, "called"); - keys.PopulateTickets(); const auto count = keys.GetPersonalizedTickets().size(); IPC::ResponseBuilder rb{ctx, 3}; From 50d541407507edc2f29ad6a46f3f17724e52f7f7 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sun, 26 May 2019 13:01:42 -0400 Subject: [PATCH 15/15] key_manager: Convert Ticket union to std::variant --- src/core/crypto/key_manager.cpp | 93 ++++++++++++++++++++------------- src/core/crypto/key_manager.h | 50 +++++++++++------- src/core/hle/service/es/es.cpp | 4 +- 3 files changed, 89 insertions(+), 58 deletions(-) diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 3c51e3dc21..46aceec3d1 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -56,6 +56,13 @@ const std::map, std::string> KEYS_VARIABLE_LENGTH{ {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, }; +namespace { +template +bool IsAllZeroArray(const std::array& array) { + return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; }); +} +} // namespace + u64 GetSignatureTypeDataSize(SignatureType type) { switch (type) { case SignatureType::RSA_4096_SHA1: @@ -85,47 +92,61 @@ u64 GetSignatureTypePaddingSize(SignatureType type) { UNREACHABLE(); } -TicketData& Ticket::GetData() { - switch (sig_type) { - case SignatureType::RSA_4096_SHA1: - case SignatureType::RSA_4096_SHA256: - return rsa_4096.data; - case SignatureType::RSA_2048_SHA1: - case SignatureType::RSA_2048_SHA256: - return rsa_2048.data; - case SignatureType::ECDSA_SHA1: - case SignatureType::ECDSA_SHA256: - return ecdsa.data; +SignatureType Ticket::GetSignatureType() const { + if (auto ticket = std::get_if(&data)) { + return ticket->sig_type; } + if (auto ticket = std::get_if(&data)) { + return ticket->sig_type; + } + if (auto ticket = std::get_if(&data)) { + return ticket->sig_type; + } + + UNREACHABLE(); +} + +TicketData& Ticket::GetData() { + if (auto ticket = std::get_if(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if(&data)) { + return ticket->data; + } + UNREACHABLE(); } const TicketData& Ticket::GetData() const { - switch (sig_type) { - case SignatureType::RSA_4096_SHA1: - case SignatureType::RSA_4096_SHA256: - return rsa_4096.data; - case SignatureType::RSA_2048_SHA1: - case SignatureType::RSA_2048_SHA256: - return rsa_2048.data; - case SignatureType::ECDSA_SHA1: - case SignatureType::ECDSA_SHA256: - return ecdsa.data; + if (auto ticket = std::get_if(&data)) { + return ticket->data; } + if (auto ticket = std::get_if(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if(&data)) { + return ticket->data; + } + UNREACHABLE(); } u64 Ticket::GetSize() const { + const auto sig_type = GetSignatureType(); + return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) + GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData); } -Ticket Ticket::SynthesizeCommon(Key128 title_key, std::array rights_id) { - Ticket out{}; +Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array& rights_id) { + RSA2048Ticket out{}; out.sig_type = SignatureType::RSA_2048_SHA256; - out.GetData().rights_id = rights_id; - out.GetData().title_key_common = title_key; - return out; + out.data.rights_id = rights_id; + out.data.title_key_common = title_key; + return Ticket{out}; } Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { @@ -208,14 +229,13 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { } } -RSAKeyPair<2048> KeyManager::GetETicketRSAKey() { - if (eticket_extended_kek == std::array{} || !HasKey(S128KeyType::ETicketRSAKek)) +RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { + if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) return {}; const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); - std::vector extended_iv(0x10); - std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); + std::vector extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10); std::array extended_dec{}; AESCipher rsa_1(eticket_final, Mode::CTR); rsa_1.SetIV(extended_iv); @@ -1001,13 +1021,14 @@ void KeyManager::PopulateTickets() { void KeyManager::SynthesizeTickets() { for (const auto& key : s128_keys) { - if (key.first.type == S128KeyType::Titlekey) { - u128 rights_id{key.first.field1, key.first.field2}; - Key128 rights_id_2; - std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); - const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); - common_tickets.insert_or_assign(rights_id, ticket); + if (key.first.type != S128KeyType::Titlekey) { + continue; } + u128 rights_id{key.first.field1, key.first.field2}; + Key128 rights_id_2; + std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); + const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); + common_tickets.insert_or_assign(rights_id, ticket); } } diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index d4e89d35c3..7265c41714 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include "common/common_funcs.h" @@ -73,33 +74,36 @@ struct TicketData { }; static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size."); -struct Ticket { +struct RSA4096Ticket { SignatureType sig_type; - union { - struct { - std::array sig_data; - INSERT_PADDING_BYTES(0x3C); - TicketData data; - } rsa_4096; + std::array sig_data; + INSERT_PADDING_BYTES(0x3C); + TicketData data; +}; - struct { - std::array sig_data; - INSERT_PADDING_BYTES(0x3C); - TicketData data; - } rsa_2048; +struct RSA2048Ticket { + SignatureType sig_type; + std::array sig_data; + INSERT_PADDING_BYTES(0x3C); + TicketData data; +}; - struct { - std::array sig_data; - INSERT_PADDING_BYTES(0x40); - TicketData data; - } ecdsa; - }; +struct ECDSATicket { + SignatureType sig_type; + std::array sig_data; + INSERT_PADDING_BYTES(0x40); + TicketData data; +}; +struct Ticket { + std::variant data; + + SignatureType GetSignatureType() const; TicketData& GetData(); const TicketData& GetData() const; u64 GetSize() const; - static Ticket SynthesizeCommon(Key128 title_key, std::array rights_id); + static Ticket SynthesizeCommon(Key128 title_key, const std::array& rights_id); }; static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); @@ -120,6 +124,12 @@ bool operator==(const RSAKeyPair& lhs, std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent); } +template +bool operator!=(const RSAKeyPair& lhs, + const RSAKeyPair& rhs) { + return !(lhs == rhs); +} + enum class KeyCategory : u8 { Standard, Title, @@ -268,7 +278,7 @@ private: void DeriveGeneralPurposeKeys(std::size_t crypto_revision); - RSAKeyPair<2048> GetETicketRSAKey(); + RSAKeyPair<2048> GetETicketRSAKey() const; void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 92fa2bef88..af70d174dc 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -234,7 +234,7 @@ private: const auto ticket = keys.GetCommonTickets().at(rights_id); - const auto write_size = std::min(ticket.GetSize(), ctx.GetWriteBufferSize()); + const auto write_size = std::min(ticket.GetSize(), ctx.GetWriteBufferSize()); ctx.WriteBuffer(&ticket, write_size); IPC::ResponseBuilder rb{ctx, 4}; @@ -253,7 +253,7 @@ private: const auto ticket = keys.GetPersonalizedTickets().at(rights_id); - const auto write_size = std::min(ticket.GetSize(), ctx.GetWriteBufferSize()); + const auto write_size = std::min(ticket.GetSize(), ctx.GetWriteBufferSize()); ctx.WriteBuffer(&ticket, write_size); IPC::ResponseBuilder rb{ctx, 4};