Add in complete Ticket support from Citra

This commit is contained in:
Pengfei
2021-07-07 12:03:06 +08:00
parent 3ade2a382f
commit 3429d9e965
7 changed files with 148 additions and 72 deletions
+1
View File
@@ -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
+4 -3
View File
@@ -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;
+39
View File
@@ -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
+67 -9
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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 {
-28
View File
@@ -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