From 5275fd2789459c1c633b2436ddb0d352018650d7 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
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<u8, 4> exponent;
 };
 
+template <size_t bit_size, size_t byte_size>
+bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs,
+                const RSAKeyPair<bit_size, byte_size>& 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 <zachhilman@gmail.com>
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<u8, 576>{} || !HasKey(S128KeyType::ETicketRSAKek))
+        return {};
+
+    const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
+
+    std::vector<u8> extended_iv(0x10);
+    std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
+    std::array<u8, 0x230> extended_dec{};
+    AESCipher<Key128> 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<Key128> 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<u8> extended_iv(0x10);
-    std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
-    std::array<u8, 0x230> extended_dec{};
-    AESCipher<Key128> 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<u128, TicketRaw>& KeyManager::GetCommonTickets() const {
+    return common_tickets;
+}
+
+const std::map<u128, TicketRaw>& 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<std::string, KeyIndex<S128KeyType>> 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<u128, TicketRaw>& GetCommonTickets() const;
+    const std::map<u128, TicketRaw>& GetPersonalizedTickets() const;
+
+    bool AddTicketCommon(TicketRaw raw);
+    bool AddTicketPersonalized(TicketRaw raw);
+
 private:
     std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
     std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
 
+    // Map from rights ID to ticket
+    std::map<u128, TicketRaw> common_tickets;
+    std::map<u128, TicketRaw> personal_tickets;
+
     std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
     std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
+    std::array<u8, 576> 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 <zachhilman@gmail.com>
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<ETicket> {
 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 <zachhilman@gmail.com>
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<u128>();
+
+        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 <zachhilman@gmail.com>
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<u32>(count);
+    }
+
 };
 
 void InstallInterfaces(SM::ServiceManager& service_manager) {

From 11f45e60159cdd215158e2c60ac08ed4e5a07439 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
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<u32>(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<u32>(count);
+    }
+
 };
 
 void InstallInterfaces(SM::ServiceManager& service_manager) {

From 5d6bf75296984eb2d4c7e1486daeb306a5fefa32 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
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<u32>(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<u128> ids;
+        std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
+                       [](const auto& pair) { return pair.first; });
+
+        out_entries = std::min<u32>(ids.size(), out_entries);
+        ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
+
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u32>(out_entries);
+    }
+
 };
 
 void InstallInterfaces(SM::ServiceManager& service_manager) {

From 669a21babb89c670746ed0a3ba89543a70f8e05e Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
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<u32>(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<u128> ids;
+        std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
+                       [](const auto& pair) { return pair.first; });
+
+        out_entries = std::min<u32>(ids.size(), out_entries);
+        ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
+
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u32>(out_entries);
+    }
+
 };
 
 void InstallInterfaces(SM::ServiceManager& service_manager) {

From 35b617b57ffb46d33886304b82ae80b40d80c042 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
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<u32>(out_entries);
     }
 
+    void GetCommonTicketSize(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto rights_id = rp.PopRaw<u128>();
+
+        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<u64>(ticket.size());
+    }
+
 };
 
 void InstallInterfaces(SM::ServiceManager& service_manager) {

From 44b0c19f6a0a9491028800ca0a10dc74c954b258 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
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<u64>(ticket.size());
     }
 
+    void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto rights_id = rp.PopRaw<u128>();
+
+        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<u64>(ticket.size());
+    }
+
 };
 
 void InstallInterfaces(SM::ServiceManager& service_manager) {

From c6a32dc077f3d55421898294ada0a5fe93400b9e Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
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<u64>(ticket.size());
     }
 
+    void GetCommonTicketData(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto rights_id = rp.PopRaw<u128>();
+
+        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<u64>(write_size);
+    }
+
 };
 
 void InstallInterfaces(SM::ServiceManager& service_manager) {

From b294b13584b308cdc7819cf9b4b3859ee3b9b3c2 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
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<u64>(write_size);
     }
 
+    void GetPersonalizedTicketData(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto rights_id = rp.PopRaw<u128>();
+
+        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<u64>(write_size);
+    }
+
+    Core::Crypto::KeyManager keys;
 };
 
 void InstallInterfaces(SM::ServiceManager& service_manager) {

From f8718ae779bbdc6a3f514b5ce141515baa97e14f Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
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::pair<S128KeyType, u64>, 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<u8, 16> 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<Key256, 2>& sd_keys, KeyManager& ke
     return Loader::ResultStatus::Success;
 }
 
-std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
+std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) {
     if (!ticket_save.IsOpen())
         return {};
 
@@ -268,14 +341,14 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
         return {};
     }
 
-    std::vector<TicketRaw> out;
+    std::vector<Ticket> 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<u64> FindTicketOffset(const std::array<u8, size>& data) {
     return offset;
 }
 
-std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
+std::optional<std::pair<Key128, Key128>> 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<u8, 0x40>{})
         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<std::pair<Key128, Key128>> 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<std::pair<Key128, Key128>> 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<u128, TicketRaw>& KeyManager::GetCommonTickets() const {
+const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const {
     return common_tickets;
 }
 
-const std::map<u128, TicketRaw>& KeyManager::GetPersonalizedTickets() const {
+const std::map<u128, Ticket>& 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 <boost/container/flat_map.hpp>
 #include <fmt/format.h>
+#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<u8, 0x10>;
 using Key256 = std::array<u8, 0x20>;
 using SHA256Hash = std::array<u8, 0x20>;
-using TicketRaw = std::array<u8, 0x400>;
+
+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<u8, 0x40> issuer;
+    union {
+        std::array<u8, 0x100> title_key_block;
+
+        struct {
+            Key128 title_key_common;
+            std::array<u8, 0xF0> 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<u8, 0x10> 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<u8, 0x200> sig_data;
+            INSERT_PADDING_BYTES(0x3C);
+            TicketData data;
+        } rsa_4096;
+
+        struct {
+            std::array<u8, 0x100> sig_data;
+            INSERT_PADDING_BYTES(0x3C);
+            TicketData data;
+        } rsa_2048;
+
+        struct {
+            std::array<u8, 0x3C> 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<u8, 0x10> 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<u128, TicketRaw>& GetCommonTickets() const;
-    const std::map<u128, TicketRaw>& GetPersonalizedTickets() const;
+    const std::map<u128, Ticket>& GetCommonTickets() const;
+    const std::map<u128, Ticket>& GetPersonalizedTickets() const;
 
-    bool AddTicketCommon(TicketRaw raw);
-    bool AddTicketPersonalized(TicketRaw raw);
+    bool AddTicketCommon(Ticket raw);
+    bool AddTicketPersonalized(Ticket raw);
 
 private:
     std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
     std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
 
     // Map from rights ID to ticket
-    std::map<u128, TicketRaw> common_tickets;
-    std::map<u128, TicketRaw> personal_tickets;
+    std::map<u128, Ticket> common_tickets;
+    std::map<u128, Ticket> personal_tickets;
 
     std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
     std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
@@ -216,11 +286,11 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo
 std::optional<Key128> DeriveSDSeed();
 Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
 
-std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
+std::vector<Ticket> 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<std::pair<Key128, Key128>> 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<std::pair<Key128, Key128>> 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<u64>(ticket.size());
+        rb.Push<u64>(ticket.GetSize());
     }
 
     void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) {
@@ -219,7 +219,7 @@ private:
 
         IPC::ResponseBuilder rb{ctx, 4};
         rb.Push(RESULT_SUCCESS);
-        rb.Push<u64>(ticket.size());
+        rb.Push<u64>(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 <zachhilman@gmail.com>
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<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
     if (issuer == std::array<u8, 0x40>{})
         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 <zachhilman@gmail.com>
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::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
     {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
 };
 
+namespace {
+template <std::size_t Size>
+bool IsAllZeroArray(const std::array<u8, Size>& 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<RSA4096Ticket>(&data)) {
+        return ticket->sig_type;
     }
+    if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+        return ticket->sig_type;
+    }
+    if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+        return ticket->sig_type;
+    }
+
+    UNREACHABLE();
+}
+
+TicketData& Ticket::GetData() {
+    if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+        return ticket->data;
+    }
+    if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+        return ticket->data;
+    }
+    if (auto ticket = std::get_if<ECDSATicket>(&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<RSA4096Ticket>(&data)) {
+        return ticket->data;
     }
+    if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+        return ticket->data;
+    }
+    if (auto ticket = std::get_if<ECDSATicket>(&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<u8, 16> rights_id) {
-    Ticket out{};
+Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& 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<u8, 576>{} || !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<u8> extended_iv(0x10);
-    std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
+    std::vector<u8> extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10);
     std::array<u8, 0x230> extended_dec{};
     AESCipher<Key128> 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 <optional>
 #include <string>
 
+#include <variant>
 #include <boost/container/flat_map.hpp>
 #include <fmt/format.h>
 #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<u8, 0x200> sig_data;
-            INSERT_PADDING_BYTES(0x3C);
-            TicketData data;
-        } rsa_4096;
+    std::array<u8, 0x200> sig_data;
+    INSERT_PADDING_BYTES(0x3C);
+    TicketData data;
+};
 
-        struct {
-            std::array<u8, 0x100> sig_data;
-            INSERT_PADDING_BYTES(0x3C);
-            TicketData data;
-        } rsa_2048;
+struct RSA2048Ticket {
+    SignatureType sig_type;
+    std::array<u8, 0x100> sig_data;
+    INSERT_PADDING_BYTES(0x3C);
+    TicketData data;
+};
 
-        struct {
-            std::array<u8, 0x3C> sig_data;
-            INSERT_PADDING_BYTES(0x40);
-            TicketData data;
-        } ecdsa;
-    };
+struct ECDSATicket {
+    SignatureType sig_type;
+    std::array<u8, 0x3C> sig_data;
+    INSERT_PADDING_BYTES(0x40);
+    TicketData data;
+};
 
+struct Ticket {
+    std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
+
+    SignatureType GetSignatureType() const;
     TicketData& GetData();
     const TicketData& GetData() const;
     u64 GetSize() const;
 
-    static Ticket SynthesizeCommon(Key128 title_key, std::array<u8, 0x10> rights_id);
+    static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
 };
 
 static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
@@ -120,6 +124,12 @@ bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs,
            std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent);
 }
 
+template <size_t bit_size, size_t byte_size>
+bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs,
+                const RSAKeyPair<bit_size, byte_size>& 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<u64>(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<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
         ctx.WriteBuffer(&ticket, write_size);
 
         IPC::ResponseBuilder rb{ctx, 4};