From 3429d9e965cb95dda635744e9ea78751975fba33 Mon Sep 17 00:00:00 2001 From: Pengfei Date: Wed, 7 Jul 2021 12:03:06 +0800 Subject: [PATCH] Add in complete Ticket support from Citra --- src/core/CMakeLists.txt | 1 + src/core/ncch/cia_builder.cpp | 7 +-- src/core/ncch/cia_common.h | 39 ++++++++++++++++ src/core/ncch/ticket.cpp | 76 ++++++++++++++++++++++++++++---- src/core/ncch/ticket.h | 68 ++++++++++++++-------------- src/core/ncch/title_metadata.cpp | 1 + src/core/ncch/title_metadata.h | 28 ------------ 7 files changed, 148 insertions(+), 72 deletions(-) create mode 100644 src/core/ncch/cia_common.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f1367fe..9106e9d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(core STATIC key/key.h ncch/cia_builder.cpp ncch/cia_builder.h + ncch/cia_common.h ncch/ncch_container.cpp ncch/ncch_container.h ncch/seed_db.cpp diff --git a/src/core/ncch/cia_builder.cpp b/src/core/ncch/cia_builder.cpp index 9a8956b..b56656a 100644 --- a/src/core/ncch/cia_builder.cpp +++ b/src/core/ncch/cia_builder.cpp @@ -77,11 +77,12 @@ bool CIABuilder::Init(const std::string& destination, TitleMetadata tmd_, // Ticket ticket_offset = Common::AlignUp(cert_offset + header.cert_size, CIA_ALIGNMENT); - header.tik_size = sizeof(Ticket); - Ticket fake_ticket = BuildFakeTicket(tmd.GetTitleID()); + const auto fake_ticket = BuildFakeTicket(tmd.GetTitleID()).GetData(); + header.tik_size = fake_ticket.size(); + file->Seek(ticket_offset, SEEK_SET); - if (file->WriteBytes(&fake_ticket, sizeof(fake_ticket)) != sizeof(fake_ticket)) { + if (file->WriteBytes(fake_ticket.data(), fake_ticket.size()) != fake_ticket.size()) { LOG_ERROR(Core, "Could not write ticket to file {}", destination); file.reset(); return false; diff --git a/src/core/ncch/cia_common.h b/src/core/ncch/cia_common.h new file mode 100644 index 0000000..4c77f1e --- /dev/null +++ b/src/core/ncch/cia_common.h @@ -0,0 +1,39 @@ +// Copyright 2018 Citra Emulator Project / 2021 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Core { + +enum TMDSignatureType : u32 { + Rsa4096Sha1 = 0x10000, + Rsa2048Sha1 = 0x10001, + EllipticSha1 = 0x10002, + Rsa4096Sha256 = 0x10003, + Rsa2048Sha256 = 0x10004, + EcdsaSha256 = 0x10005 +}; + +inline u32 GetSignatureSize(u32 signature_type) { + switch (signature_type) { + case Rsa4096Sha1: + case Rsa4096Sha256: + return 0x200; + + case Rsa2048Sha1: + case Rsa2048Sha256: + return 0x100; + + case EllipticSha1: + case EcdsaSha256: + return 0x3C; + } + + LOG_ERROR(Common_Filesystem, "Tried to read ticket with bad signature {}", signature_type); + return 0; +} + +} // namespace Core diff --git a/src/core/ncch/ticket.cpp b/src/core/ncch/ticket.cpp index 0a7e18d..872f764 100644 --- a/src/core/ncch/ticket.cpp +++ b/src/core/ncch/ticket.cpp @@ -4,10 +4,55 @@ #include #include +#include "common/alignment.h" +#include "common/assert.h" +#include "core/ncch/cia_common.h" #include "core/ncch/ticket.h" namespace Core { +bool Ticket::Load(const std::vector file_data, std::size_t offset) { + std::size_t total_size = static_cast(file_data.size() - offset); + if (total_size < sizeof(u32)) + return false; + + std::memcpy(&signature_type, &file_data[offset], sizeof(u32)); + + // Signature lengths are variable, and the body follows the signature + u32 signature_size = GetSignatureSize(signature_type); + if (signature_size == 0) { + return false; + } + + // The ticket body start position is rounded to the nearest 0x40 after the signature + std::size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40); + std::size_t body_end = body_start + sizeof(Body); + + if (total_size < body_end) + return false; + + // Read signature + ticket body + signature.resize(signature_size); + memcpy(signature.data(), &file_data[offset + sizeof(u32)], signature_size); + memcpy(&body, &file_data[offset + body_start], sizeof(Body)); + + return true; +} + +std::vector Ticket::GetData() const { + u32 signature_size = GetSignatureSize(signature_type); + ASSERT(signature_size != 0); + + const std::size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40); + const std::size_t body_end = body_start + sizeof(Body); + + std::vector out(body_end); + std::memcpy(out.data(), &signature_type, sizeof(signature_type)); + std::memcpy(out.data() + sizeof(signature_type), signature.data(), signature.size()); + std::memcpy(out.data() + body_start, &body, sizeof(Body)); + return out; +} + constexpr std::string_view TicketIssuer = "Root-CA00000003-XS0000000c"; // TODO: Make use of this? @@ -23,17 +68,30 @@ constexpr std::array TicketContentIndex{ Ticket BuildFakeTicket(u64 title_id) { Ticket ticket{}; ticket.signature_type = 0x10004; // RSA_2048 SHA256 + std::memset(ticket.signature.data(), 0xFF, ticket.signature.size()); - std::memcpy(ticket.issuer.data(), TicketIssuer.data(), TicketIssuer.size()); - std::memset(ticket.ecc_public_key.data(), 0xFF, ticket.ecc_public_key.size()); - ticket.version = 0x01; - std::memset(ticket.title_key.data(), 0xFF, ticket.title_key.size()); - ticket.title_id = title_id; - ticket.common_key_index = 0x00; - ticket.audit = 0x01; - std::memcpy(ticket.content_index.data(), TicketContentIndex.data(), TicketContentIndex.size()); + + auto& body = ticket.body; + std::memcpy(body.issuer.data(), TicketIssuer.data(), TicketIssuer.size()); + std::memset(body.ecc_public_key.data(), 0xFF, body.ecc_public_key.size()); + body.version = 0x01; + std::memset(body.title_key.data(), 0xFF, body.title_key.size()); + body.title_id = title_id; + body.common_key_index = 0x00; + body.audit = 0x01; + std::memcpy(body.content_index.data(), TicketContentIndex.data(), TicketContentIndex.size()); // GodMode9 by default sets all remaining 0x80 bytes to 0xFF, but legit tickets only set 0x20 - std::memset(ticket.content_index.data() + TicketContentIndex.size(), 0xFF, 0x20); + std::memset(body.content_index.data() + TicketContentIndex.size(), 0xFF, 0x20); + return ticket; +} + +Ticket BuildStandardTicket(u64 title_id, Ticket legit_ticket) { + Ticket ticket = BuildFakeTicket(title_id); + + // Put in the title key from the legit ticket + ticket.body.title_key.swap(legit_ticket.body.title_key); + ticket.body.common_key_index = legit_ticket.body.common_key_index; + return ticket; } diff --git a/src/core/ncch/ticket.h b/src/core/ncch/ticket.h index adc816c..0ff4400 100644 --- a/src/core/ncch/ticket.h +++ b/src/core/ncch/ticket.h @@ -12,41 +12,45 @@ namespace Core { -/** - * Structure of the ticket. - * The ticket we are using is RSA_2048 SHA256. - */ +class Ticket { +public: #pragma pack(push, 1) -struct Ticket { - u32_be signature_type; - std::array signature; - INSERT_PADDING_BYTES(0x3C); - std::array issuer; - std::array ecc_public_key; - u8 version; - u8 ca_crl_version; - u8 signer_crl_version; - std::array title_key; - INSERT_PADDING_BYTES(1); - u64_be ticket_id; - u32_be console_id; - u64_be title_id; - INSERT_PADDING_BYTES(2); - u16_be ticket_title_version; - INSERT_PADDING_BYTES(8); - u8 license_type; - u8 common_key_index; - INSERT_PADDING_BYTES(0x2A); - u32_be eshop_account_id; - INSERT_PADDING_BYTES(1); - u8 audit; - INSERT_PADDING_BYTES(0x42); - std::array limits; - std::array content_index; -}; -static_assert(sizeof(Ticket) == 0x350, "Ticket size is wrong"); + struct Body { + std::array issuer; + std::array ecc_public_key; + u8 version; + u8 ca_crl_version; + u8 signer_crl_version; + std::array title_key; + INSERT_PADDING_BYTES(1); + u64_be ticket_id; + u32_be console_id; + u64_be title_id; + INSERT_PADDING_BYTES(2); + u16_be ticket_title_version; + INSERT_PADDING_BYTES(8); + u8 license_type; + u8 common_key_index; + INSERT_PADDING_BYTES(0x2A); + u32_be eshop_account_id; + INSERT_PADDING_BYTES(1); + u8 audit; + INSERT_PADDING_BYTES(0x42); + std::array limits; + std::array content_index; + }; + static_assert(sizeof(Body) == 0x210, "Ticket body structure size is wrong"); #pragma pack(pop) + bool Load(const std::vector file_data, std::size_t offset = 0); + std::vector GetData() const; + + Body body; + u32_be signature_type; + std::vector signature; +}; + Ticket BuildFakeTicket(u64 title_id); +Ticket BuildStandardTicket(u64 title_id, Ticket legit_ticket); } // namespace Core diff --git a/src/core/ncch/title_metadata.cpp b/src/core/ncch/title_metadata.cpp index 02ffdba..7390e5c 100644 --- a/src/core/ncch/title_metadata.cpp +++ b/src/core/ncch/title_metadata.cpp @@ -9,6 +9,7 @@ #include "common/assert.h" #include "common/file_util.h" #include "common/logging/log.h" +#include "core/ncch/cia_common.h" #include "core/ncch/title_metadata.h" namespace Core { diff --git a/src/core/ncch/title_metadata.h b/src/core/ncch/title_metadata.h index 7ace44c..c1e885e 100644 --- a/src/core/ncch/title_metadata.h +++ b/src/core/ncch/title_metadata.h @@ -23,34 +23,6 @@ enum TMDContentTypeFlag : u16 { enum TMDContentIndex { Main = 0, Manual = 1, DLP = 2 }; -enum TMDSignatureType : u32 { - Rsa4096Sha1 = 0x10000, - Rsa2048Sha1 = 0x10001, - EllipticSha1 = 0x10002, - Rsa4096Sha256 = 0x10003, - Rsa2048Sha256 = 0x10004, - EcdsaSha256 = 0x10005 -}; - -inline u32 GetSignatureSize(u32 signature_type) { - switch (signature_type) { - case Rsa4096Sha1: - case Rsa4096Sha256: - return 0x200; - - case Rsa2048Sha1: - case Rsa2048Sha256: - return 0x100; - - case EllipticSha1: - case EcdsaSha256: - return 0x3C; - } - - LOG_ERROR(Common_Filesystem, "Tried to read ticket with bad signature {}", signature_type); - return 0; -} - /** * Helper which implements an interface to read and write Title Metadata (TMD) files. * If a file path is provided and the file exists, it can be parsed and used, otherwise