mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 00:38:58 +00:00
Changes to TMD and CIA builder
Prepare for legit CIA.
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/modes.h>
|
||||
#include <cryptopp/sha.h>
|
||||
#include "common/alignment.h"
|
||||
#include "core/importer.h"
|
||||
@@ -34,6 +36,10 @@ public:
|
||||
sha.Final(out);
|
||||
}
|
||||
|
||||
bool VerifyHash(u8* out) {
|
||||
sha.Verify(out);
|
||||
}
|
||||
|
||||
std::size_t Write(const char* data, std::size_t length) override {
|
||||
const std::size_t length_written = FileUtil::IOFile::Write(data, length);
|
||||
sha.Update(reinterpret_cast<const CryptoPP::byte*>(data), length_written);
|
||||
@@ -70,6 +76,13 @@ bool CIABuilder::Init(CIABuildType type_, const std::string& destination, TitleM
|
||||
chunk.type &= ~0x01;
|
||||
}
|
||||
}
|
||||
if (type == CIABuildType::Legit || type == CIABuildType::PirateLegit) {
|
||||
// Check for legit TMD
|
||||
if (!tmd.VerifyHashes() || !tmd.ValidateSignature()) {
|
||||
LOG_ERROR(Core, "TMD is not legit");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
header.header_size = sizeof(header);
|
||||
// Header will be written in Finalize
|
||||
@@ -123,14 +136,18 @@ bool CIABuilder::WriteCert(const std::string& certs_db_path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool FindLegitTicket(Ticket& out, u64 title_id, const std::string& ticket_db_path) {
|
||||
static bool FindLegitTicket(Ticket& ticket, u64 title_id, const std::string& ticket_db_path) {
|
||||
TicketDB ticket_db(ticket_db_path);
|
||||
if (ticket_db.IsGood() && ticket_db.tickets.count(title_id)) {
|
||||
out = ticket_db.tickets.at(title_id);
|
||||
ticket = ticket_db.tickets.at(title_id);
|
||||
if (!ticket.ValidateSignature()) {
|
||||
LOG_ERROR(Core, "Ticket in ticket.db for {:016x} is not legit", title_id);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_ERROR(Core, "Could not find legit ticket for {:016x}", title_id);
|
||||
LOG_ERROR(Core, "Ticket for {:016x} does not exist in ticket.db", title_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -160,6 +177,25 @@ static Ticket BuildStandardTicket(u64 title_id, const std::string& ticket_db_pat
|
||||
return ticket;
|
||||
}
|
||||
|
||||
static Key::AESKey GetTitleKey(const Ticket& ticket) {
|
||||
Key::SelectCommonKeyIndex(ticket.body.common_key_index);
|
||||
if (!Key::IsNormalKeyAvailable(Key::TicketCommonKey)) {
|
||||
LOG_ERROR(Core, "Ticket common key is not available");
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto ticket_key = Key::GetNormalKey(Key::TicketCommonKey);
|
||||
Key::AESKey ctr{};
|
||||
std::memcpy(ctr.data(), &ticket.body.title_id, 8);
|
||||
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption aes;
|
||||
aes.SetKeyWithIV(ticket_key.data(), ticket_key.size(), ctr.data());
|
||||
|
||||
Key::AESKey title_key = ticket.body.title_key;
|
||||
aes.ProcessData(title_key.data(), title_key.data(), title_key.size());
|
||||
return title_key;
|
||||
}
|
||||
|
||||
bool CIABuilder::WriteTicket(const std::string& ticket_db_path,
|
||||
const std::string& enc_title_keys_bin_path) {
|
||||
|
||||
@@ -173,6 +209,7 @@ bool CIABuilder::WriteTicket(const std::string& ticket_db_path,
|
||||
} else {
|
||||
ticket = BuildStandardTicket(title_id, ticket_db_path, enc_title_keys_bin_path);
|
||||
}
|
||||
title_key = GetTitleKey(ticket);
|
||||
|
||||
header.tik_size = ticket.GetSize();
|
||||
|
||||
@@ -213,7 +250,15 @@ bool CIABuilder::AddContent(u16 content_id, NCCHContainer& ncch) {
|
||||
|
||||
auto& tmd_chunk = tmd.GetContentChunkByID(content_id);
|
||||
header.SetContentPresent(tmd_chunk.index);
|
||||
file->GetHash(tmd_chunk.hash.data());
|
||||
|
||||
if (type == CIABuildType::Standard) { // Fix hash
|
||||
file->GetHash(tmd_chunk.hash.data());
|
||||
} else { // Verify hash
|
||||
if (!file->VerifyHash(tmd_chunk.hash.data())) {
|
||||
LOG_ERROR(Core, "Hash dismatch for content {}", content_id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
file->SetHashEnabled(false);
|
||||
|
||||
// DLCs do not have a meta
|
||||
@@ -252,6 +297,9 @@ bool CIABuilder::Finalize() {
|
||||
}
|
||||
|
||||
// Write TMD
|
||||
if (type == CIABuildType::Standard) {
|
||||
tmd.FixHashes();
|
||||
}
|
||||
file->Seek(tmd_offset, SEEK_SET);
|
||||
if (tmd.Save(*file) != ResultStatus::Success) {
|
||||
file.reset();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/progress_callback.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/key/key.h"
|
||||
#include "core/ncch/ncch_container.h"
|
||||
#include "core/ncch/title_metadata.h"
|
||||
#include "core/quick_decryptor.h"
|
||||
@@ -106,6 +107,7 @@ private:
|
||||
Metadata meta{};
|
||||
|
||||
TitleMetadata tmd;
|
||||
Key::AESKey title_key{};
|
||||
|
||||
std::size_t cert_offset{};
|
||||
std::size_t ticket_offset{};
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
#include <cryptopp/rsa.h>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/ncch/cia_common.h"
|
||||
#include "core/ncch/ticket.h"
|
||||
|
||||
@@ -36,6 +38,14 @@ bool Ticket::Save(FileUtil::IOFile& file) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ticket::ValidateSignature() const {
|
||||
const auto issuer =
|
||||
Common::StringFromFixedZeroTerminatedBuffer(body.issuer.data(), body.issuer.size());
|
||||
return signature.Verify(issuer, [this](CryptoPP::PK_MessageAccumulator* message) {
|
||||
message->Update(reinterpret_cast<const u8*>(&body), sizeof(body));
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t Ticket::GetSize() const {
|
||||
return signature.GetSize() + sizeof(body);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class Ticket {
|
||||
public:
|
||||
#pragma pack(push, 1)
|
||||
struct Body {
|
||||
std::array<u8, 0x40> issuer;
|
||||
std::array<char, 0x40> issuer;
|
||||
std::array<u8, 0x3C> ecc_public_key;
|
||||
u8 version;
|
||||
u8 ca_crl_version;
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
|
||||
bool Load(const std::vector<u8> file_data, std::size_t offset = 0);
|
||||
bool Save(FileUtil::IOFile& file) const;
|
||||
bool ValidateSignature() const;
|
||||
std::size_t GetSize() const;
|
||||
|
||||
Signature signature;
|
||||
|
||||
@@ -62,6 +62,25 @@ ResultStatus TitleMetadata::Save(FileUtil::IOFile& file) {
|
||||
return ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Write our TMD body, then write each of our ContentChunks
|
||||
if (file.WriteBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body))
|
||||
return ResultStatus::Error;
|
||||
|
||||
for (u16 i = 0; i < tmd_body.content_count; i++) {
|
||||
ContentChunk chunk = tmd_chunks[i];
|
||||
if (file.WriteBytes(&chunk, sizeof(ContentChunk)) != sizeof(ContentChunk))
|
||||
return ResultStatus::Error;
|
||||
}
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus TitleMetadata::Save(const std::string& file_path) {
|
||||
FileUtil::IOFile file(file_path, "wb");
|
||||
return Save(file);
|
||||
}
|
||||
|
||||
void TitleMetadata::FixHashes() {
|
||||
// Update our TMD body values and hashes
|
||||
tmd_body.content_count = static_cast<u16>(tmd_chunks.size());
|
||||
|
||||
@@ -80,26 +99,43 @@ ResultStatus TitleMetadata::Save(FileUtil::IOFile& file) {
|
||||
|
||||
CryptoPP::SHA256 contentinfo_hash;
|
||||
for (std::size_t i = 0; i < tmd_body.contentinfo.size(); i++) {
|
||||
chunk_hash.Update(reinterpret_cast<u8*>(&tmd_body.contentinfo[i]), sizeof(ContentInfo));
|
||||
contentinfo_hash.Update(reinterpret_cast<u8*>(&tmd_body.contentinfo[i]),
|
||||
sizeof(ContentInfo));
|
||||
}
|
||||
chunk_hash.Final(tmd_body.contentinfo_hash.data());
|
||||
|
||||
// Write our TMD body, then write each of our ContentChunks
|
||||
if (file.WriteBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body))
|
||||
return ResultStatus::Error;
|
||||
|
||||
for (u16 i = 0; i < tmd_body.content_count; i++) {
|
||||
ContentChunk chunk = tmd_chunks[i];
|
||||
if (file.WriteBytes(&chunk, sizeof(ContentChunk)) != sizeof(ContentChunk))
|
||||
return ResultStatus::Error;
|
||||
}
|
||||
|
||||
return ResultStatus::Success;
|
||||
contentinfo_hash.Final(tmd_body.contentinfo_hash.data());
|
||||
}
|
||||
|
||||
ResultStatus TitleMetadata::Save(const std::string& file_path) {
|
||||
FileUtil::IOFile file(file_path, "wb");
|
||||
return Save(file);
|
||||
bool TitleMetadata::VerifyHashes() const {
|
||||
// This can probably be simplified to just checking contentinfo 0 (as above), but do the full
|
||||
// thing for completeness.
|
||||
// TODO: Is this what index and command_count actually mean?
|
||||
CryptoPP::SHA256 contentinfo_hash;
|
||||
for (std::size_t i = 0; i < tmd_body.contentinfo.size(); i++) {
|
||||
contentinfo_hash.Update(reinterpret_cast<const u8*>(&tmd_body.contentinfo[i]),
|
||||
sizeof(ContentInfo));
|
||||
const std::size_t offset = tmd_body.contentinfo[i].index;
|
||||
const std::size_t count = tmd_body.contentinfo[i].command_count;
|
||||
if (count == 0) {
|
||||
continue;
|
||||
}
|
||||
if (offset + count > tmd_body.content_count) { // sanity check
|
||||
LOG_INFO(Core, "Index / count out of bound for content info record {}", i);
|
||||
return false;
|
||||
}
|
||||
CryptoPP::SHA256 chunk_hash;
|
||||
for (std::size_t j = offset; j < offset + count; ++j) {
|
||||
chunk_hash.Update(reinterpret_cast<const u8*>(&tmd_chunks[j]), sizeof(ContentChunk));
|
||||
}
|
||||
if (!chunk_hash.Verify(tmd_body.contentinfo[i].hash.data())) {
|
||||
LOG_ERROR(Core, "Chunk hash dismatch for content info record {}", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!contentinfo_hash.Verify(tmd_body.contentinfo_hash.data())) {
|
||||
LOG_ERROR(Core, "Content info hash dismatch");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TitleMetadata::ValidateSignature() const {
|
||||
|
||||
@@ -83,6 +83,8 @@ public:
|
||||
ResultStatus Save(FileUtil::IOFile& file);
|
||||
ResultStatus Save(const std::string& file_path);
|
||||
|
||||
void FixHashes();
|
||||
bool VerifyHashes() const;
|
||||
bool ValidateSignature() const;
|
||||
|
||||
std::size_t GetSize() const;
|
||||
|
||||
Reference in New Issue
Block a user