mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-02 16:49:04 +00:00
Add in complete Ticket support from Citra
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -4,10 +4,55 @@
|
||||
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
#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<u8> file_data, std::size_t offset) {
|
||||
std::size_t total_size = static_cast<std::size_t>(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<u8> 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<u8> 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<u8, 44> 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;
|
||||
}
|
||||
|
||||
|
||||
+36
-32
@@ -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<u8, 0x100> signature;
|
||||
INSERT_PADDING_BYTES(0x3C);
|
||||
std::array<u8, 0x40> issuer;
|
||||
std::array<u8, 0x3C> ecc_public_key;
|
||||
u8 version;
|
||||
u8 ca_crl_version;
|
||||
u8 signer_crl_version;
|
||||
std::array<u8, 0x10> 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<u8, 0x40> limits;
|
||||
std::array<u8, 0xAC> content_index;
|
||||
};
|
||||
static_assert(sizeof(Ticket) == 0x350, "Ticket size is wrong");
|
||||
struct Body {
|
||||
std::array<u8, 0x40> issuer;
|
||||
std::array<u8, 0x3C> ecc_public_key;
|
||||
u8 version;
|
||||
u8 ca_crl_version;
|
||||
u8 signer_crl_version;
|
||||
std::array<u8, 0x10> 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<u8, 0x40> limits;
|
||||
std::array<u8, 0xAC> content_index;
|
||||
};
|
||||
static_assert(sizeof(Body) == 0x210, "Ticket body structure size is wrong");
|
||||
#pragma pack(pop)
|
||||
|
||||
bool Load(const std::vector<u8> file_data, std::size_t offset = 0);
|
||||
std::vector<u8> GetData() const;
|
||||
|
||||
Body body;
|
||||
u32_be signature_type;
|
||||
std::vector<u8> signature;
|
||||
};
|
||||
|
||||
Ticket BuildFakeTicket(u64 title_id);
|
||||
Ticket BuildStandardTicket(u64 title_id, Ticket legit_ticket);
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user