mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 00:38:58 +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
|
key/key.h
|
||||||
ncch/cia_builder.cpp
|
ncch/cia_builder.cpp
|
||||||
ncch/cia_builder.h
|
ncch/cia_builder.h
|
||||||
|
ncch/cia_common.h
|
||||||
ncch/ncch_container.cpp
|
ncch/ncch_container.cpp
|
||||||
ncch/ncch_container.h
|
ncch/ncch_container.h
|
||||||
ncch/seed_db.cpp
|
ncch/seed_db.cpp
|
||||||
|
|||||||
@@ -77,11 +77,12 @@ bool CIABuilder::Init(const std::string& destination, TitleMetadata tmd_,
|
|||||||
|
|
||||||
// Ticket
|
// Ticket
|
||||||
ticket_offset = Common::AlignUp(cert_offset + header.cert_size, CIA_ALIGNMENT);
|
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);
|
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);
|
LOG_ERROR(Core, "Could not write ticket to file {}", destination);
|
||||||
file.reset();
|
file.reset();
|
||||||
return false;
|
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 <cstring>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include "common/alignment.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "core/ncch/cia_common.h"
|
||||||
#include "core/ncch/ticket.h"
|
#include "core/ncch/ticket.h"
|
||||||
|
|
||||||
namespace Core {
|
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";
|
constexpr std::string_view TicketIssuer = "Root-CA00000003-XS0000000c";
|
||||||
|
|
||||||
// TODO: Make use of this?
|
// TODO: Make use of this?
|
||||||
@@ -23,17 +68,30 @@ constexpr std::array<u8, 44> TicketContentIndex{
|
|||||||
Ticket BuildFakeTicket(u64 title_id) {
|
Ticket BuildFakeTicket(u64 title_id) {
|
||||||
Ticket ticket{};
|
Ticket ticket{};
|
||||||
ticket.signature_type = 0x10004; // RSA_2048 SHA256
|
ticket.signature_type = 0x10004; // RSA_2048 SHA256
|
||||||
|
|
||||||
std::memset(ticket.signature.data(), 0xFF, ticket.signature.size());
|
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());
|
auto& body = ticket.body;
|
||||||
ticket.version = 0x01;
|
std::memcpy(body.issuer.data(), TicketIssuer.data(), TicketIssuer.size());
|
||||||
std::memset(ticket.title_key.data(), 0xFF, ticket.title_key.size());
|
std::memset(body.ecc_public_key.data(), 0xFF, body.ecc_public_key.size());
|
||||||
ticket.title_id = title_id;
|
body.version = 0x01;
|
||||||
ticket.common_key_index = 0x00;
|
std::memset(body.title_key.data(), 0xFF, body.title_key.size());
|
||||||
ticket.audit = 0x01;
|
body.title_id = title_id;
|
||||||
std::memcpy(ticket.content_index.data(), TicketContentIndex.data(), TicketContentIndex.size());
|
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
|
// 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;
|
return ticket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+36
-32
@@ -12,41 +12,45 @@
|
|||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
/**
|
class Ticket {
|
||||||
* Structure of the ticket.
|
public:
|
||||||
* The ticket we are using is RSA_2048 SHA256.
|
|
||||||
*/
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct Ticket {
|
struct Body {
|
||||||
u32_be signature_type;
|
std::array<u8, 0x40> issuer;
|
||||||
std::array<u8, 0x100> signature;
|
std::array<u8, 0x3C> ecc_public_key;
|
||||||
INSERT_PADDING_BYTES(0x3C);
|
u8 version;
|
||||||
std::array<u8, 0x40> issuer;
|
u8 ca_crl_version;
|
||||||
std::array<u8, 0x3C> ecc_public_key;
|
u8 signer_crl_version;
|
||||||
u8 version;
|
std::array<u8, 0x10> title_key;
|
||||||
u8 ca_crl_version;
|
INSERT_PADDING_BYTES(1);
|
||||||
u8 signer_crl_version;
|
u64_be ticket_id;
|
||||||
std::array<u8, 0x10> title_key;
|
u32_be console_id;
|
||||||
INSERT_PADDING_BYTES(1);
|
u64_be title_id;
|
||||||
u64_be ticket_id;
|
INSERT_PADDING_BYTES(2);
|
||||||
u32_be console_id;
|
u16_be ticket_title_version;
|
||||||
u64_be title_id;
|
INSERT_PADDING_BYTES(8);
|
||||||
INSERT_PADDING_BYTES(2);
|
u8 license_type;
|
||||||
u16_be ticket_title_version;
|
u8 common_key_index;
|
||||||
INSERT_PADDING_BYTES(8);
|
INSERT_PADDING_BYTES(0x2A);
|
||||||
u8 license_type;
|
u32_be eshop_account_id;
|
||||||
u8 common_key_index;
|
INSERT_PADDING_BYTES(1);
|
||||||
INSERT_PADDING_BYTES(0x2A);
|
u8 audit;
|
||||||
u32_be eshop_account_id;
|
INSERT_PADDING_BYTES(0x42);
|
||||||
INSERT_PADDING_BYTES(1);
|
std::array<u8, 0x40> limits;
|
||||||
u8 audit;
|
std::array<u8, 0xAC> content_index;
|
||||||
INSERT_PADDING_BYTES(0x42);
|
};
|
||||||
std::array<u8, 0x40> limits;
|
static_assert(sizeof(Body) == 0x210, "Ticket body structure size is wrong");
|
||||||
std::array<u8, 0xAC> content_index;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(Ticket) == 0x350, "Ticket size is wrong");
|
|
||||||
#pragma pack(pop)
|
#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 BuildFakeTicket(u64 title_id);
|
||||||
|
Ticket BuildStandardTicket(u64 title_id, Ticket legit_ticket);
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/ncch/cia_common.h"
|
||||||
#include "core/ncch/title_metadata.h"
|
#include "core/ncch/title_metadata.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|||||||
@@ -23,34 +23,6 @@ enum TMDContentTypeFlag : u16 {
|
|||||||
|
|
||||||
enum TMDContentIndex { Main = 0, Manual = 1, DLP = 2 };
|
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.
|
* 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
|
* If a file path is provided and the file exists, it can be parsed and used, otherwise
|
||||||
|
|||||||
Reference in New Issue
Block a user