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);