Add Legit CIA building

This commit is contained in:
Pengfei
2021-07-31 17:28:00 +08:00
parent 75f79e10d1
commit aed564fb25
7 changed files with 120 additions and 44 deletions
+4 -3
View File
@@ -649,8 +649,9 @@ void SDMCImporter::AbortDumpCXI() {
dump_cxi_ncch->AbortDecryptToFile();
}
bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, std::string destination,
const Common::ProgressCallback& callback, bool auto_filename) {
bool SDMCImporter::BuildCIA(CIABuildType type, const ContentSpecifier& specifier,
std::string destination, const Common::ProgressCallback& callback,
bool auto_filename) {
if (config.certs_db_path.empty()) {
LOG_ERROR(Core, "Missing certs.db");
@@ -693,7 +694,7 @@ bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, std::string desti
}
}
const bool ret = cia_builder->Init(CIABuildType::Standard, destination, tmd, config,
const bool ret = cia_builder->Init(type, destination, tmd, config,
FileUtil::GetDirectoryTreeSize(physical_path), callback);
if (!ret) {
FileUtil::Delete(destination);
+2 -1
View File
@@ -10,6 +10,7 @@
#include <vector>
#include "common/common_types.h"
#include "common/progress_callback.h"
#include "core/ncch/cia_common.h"
namespace Core {
@@ -143,7 +144,7 @@ public:
* Blocks, but can be aborted on another thread.
* @return true on success, false otherwise
*/
bool BuildCIA(const ContentSpecifier& specifier, std::string destination,
bool BuildCIA(CIABuildType type, const ContentSpecifier& specifier, std::string destination,
const Common::ProgressCallback& callback, bool auto_filename = false);
/**
+99 -32
View File
@@ -37,12 +37,14 @@ public:
}
bool VerifyHash(u8* out) {
sha.Verify(out);
return 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);
if (hash_enabled) {
sha.Update(reinterpret_cast<const CryptoPP::byte*>(data), length_written);
}
return length_written;
}
@@ -80,6 +82,7 @@ bool CIABuilder::Init(CIABuildType type_, const std::string& destination, TitleM
// Check for legit TMD
if (!tmd.VerifyHashes() || !tmd.ValidateSignature()) {
LOG_ERROR(Core, "TMD is not legit");
file.reset();
return false;
}
}
@@ -188,7 +191,7 @@ static Key::AESKey GetTitleKey(const Ticket& ticket) {
Key::AESKey ctr{};
std::memcpy(ctr.data(), &ticket.body.title_id, 8);
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption aes;
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption aes;
aes.SetKeyWithIV(ticket_key.data(), ticket_key.size(), ctr.data());
Key::AESKey title_key = ticket.body.title_key;
@@ -222,44 +225,104 @@ bool CIABuilder::WriteTicket(const std::string& ticket_db_path,
return true;
}
class CIAEncryptAndHash final : public CryptoFunc {
public:
explicit CIAEncryptAndHash(const Key::AESKey& key, const Key::AESKey& iv) {
aes.SetKeyWithIV(key.data(), key.size(), iv.data());
}
~CIAEncryptAndHash() override = default;
void ProcessData(u8* data, std::size_t size) override {
sha.Update(data, size);
aes.ProcessData(data, data, size);
}
bool VerifyHash(const u8* hash) {
return sha.Verify(hash);
}
private:
CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption aes;
CryptoPP::SHA256 sha;
};
bool CIABuilder::AddContent(u16 content_id, NCCHContainer& ncch) {
file->Seek(written, SEEK_SET); // To enforce alignment
file->SetHashEnabled(true);
{
std::lock_guard lock{abort_ncch_mutex};
abort_ncch = &ncch;
}
const auto ret = ncch.DecryptToFile(file, [this](std::size_t current, std::size_t total) {
callback(written + current, total_size);
});
{
std::lock_guard lock{abort_ncch_mutex};
abort_ncch = nullptr;
}
if (ret != ResultStatus::Success) {
file.reset();
if (ncch.Load() != ResultStatus::Success) {
return false;
}
written = Common::AlignUp(file->Tell(), CIA_ALIGNMENT);
header.content_size = written - content_offset;
file->Seek(written, SEEK_SET); // To enforce alignment
auto& tmd_chunk = tmd.GetContentChunkByID(content_id);
header.SetContentPresent(tmd_chunk.index);
const auto progress_callback = [this](std::size_t current, std::size_t total) {
callback(written + current, total_size);
};
if (type == CIABuildType::Standard) {
// Decrypt the NCCH. We created a HashedFile to transparently calculate the hash as there
// is no easy way to get decrypted NCCH content otherwise.
file->SetHashEnabled(true);
{
std::lock_guard lock{abort_ncch_mutex};
abort_ncch = &ncch;
}
const auto ret = ncch.DecryptToFile(file, progress_callback);
{
std::lock_guard lock{abort_ncch_mutex};
abort_ncch = nullptr;
}
if (type == CIABuildType::Standard) { // Fix hash
if (ret != ResultStatus::Success) {
file.reset();
return false;
}
file->GetHash(tmd_chunk.hash.data());
} else { // Verify hash
if (!file->VerifyHash(tmd_chunk.hash.data())) {
file->SetHashEnabled(false);
} else {
ncch.file->Seek(0, SEEK_SET);
// Calculate IV
Key::AESKey iv{};
std::memcpy(iv.data(), &tmd_chunk.index, sizeof(tmd_chunk.index));
const bool is_encrypted = static_cast<u16>(tmd_chunk.type) & 0x01;
// For encrypted content, the hashes are calculated before CIA/CDN encryption.
// So we have to add hash calculation to the CryptoFunc of the QuickDecryptor.
// For unencrypted content, we can just use HashedFile's hashing.
std::shared_ptr<CIAEncryptAndHash> crypto;
if (is_encrypted) {
crypto = std::make_shared<CIAEncryptAndHash>(title_key, iv);
} else { // crypto left to be null
file->SetHashEnabled(true);
}
decryptor.SetCrypto(crypto);
if (!decryptor.CryptAndWriteFile(ncch.file, ncch.file->GetSize(), file,
progress_callback)) {
file.reset();
return false;
}
// Verify the hash
bool verified{};
if (is_encrypted) {
verified = crypto->VerifyHash(tmd_chunk.hash.data());
} else {
verified = file->VerifyHash(tmd_chunk.hash.data());
file->SetHashEnabled(false);
}
if (!verified) {
LOG_ERROR(Core, "Hash dismatch for content {}", content_id);
file.reset();
return false;
}
}
file->SetHashEnabled(false);
written = Common::AlignUp(file->Tell(), CIA_ALIGNMENT);
header.content_size = written - content_offset;
header.SetContentPresent(tmd_chunk.index);
// DLCs do not have a meta
if (tmd_chunk.index != TMDContentIndex::Main || (tmd.GetTitleID() >> 32) == 0x0004008c) {
@@ -322,9 +385,13 @@ bool CIABuilder::Finalize() {
}
void CIABuilder::Abort() {
std::lock_guard lock{abort_ncch_mutex};
if (abort_ncch) {
abort_ncch->AbortDecryptToFile();
if (type == CIABuildType::Standard) { // Abort NCCH decryption
std::lock_guard lock{abort_ncch_mutex};
if (abort_ncch) {
abort_ncch->AbortDecryptToFile();
}
} else { // Abort the decryptor
decryptor.Abort();
}
}
+3 -6
View File
@@ -11,6 +11,7 @@
#include "common/progress_callback.h"
#include "common/swap.h"
#include "core/key/key.h"
#include "core/ncch/cia_common.h"
#include "core/ncch/ncch_container.h"
#include "core/ncch/title_metadata.h"
#include "core/quick_decryptor.h"
@@ -26,12 +27,6 @@ constexpr std::size_t CIA_METADATA_SIZE = 0x3AC0;
struct Config;
class HashedFile;
enum class CIABuildType {
Standard, /// Decrypted CIA with generalized ticket
PirateLegit, /// Uses legit TMD and encryption, but with generalized ticket
Legit, /// Fully legit, with personal ticket containing console ID and eshop account
};
class CIABuilder {
public:
explicit CIABuilder();
@@ -122,6 +117,8 @@ private:
// The NCCH to abort on
std::mutex abort_ncch_mutex;
NCCHContainer* abort_ncch{};
QuickDecryptor decryptor;
};
} // namespace Core
+6
View File
@@ -16,4 +16,10 @@ constexpr std::array<const char*, 3> CIACertNames{{
"Root-CA00000003-CP0000000b",
}};
enum class CIABuildType {
Standard, /// Decrypted CIA with generalized ticket
PirateLegit, /// Uses legit TMD and encryption, but with generalized ticket
Legit, /// Fully legit, with personal ticket containing console ID and eshop account
};
} // namespace Core
+2
View File
@@ -318,6 +318,8 @@ private:
// Used for DecryptToFile
QuickDecryptor decryptor;
std::atomic_bool aborted{false};
friend class CIABuilder;
};
/**
+4 -2
View File
@@ -798,7 +798,8 @@ void ImportDialog::StartBuildingCIASingle(const Core::ContentSpecifier& specifie
auto* job = new SimpleJob(
this,
[this, specifier, path](const Common::ProgressCallback& callback) {
return importer.BuildCIA(specifier, path.toStdString(), callback);
return importer.BuildCIA(Core::CIABuildType::Standard, specifier, path.toStdString(),
callback);
},
[this] { importer.AbortBuildCIA(); });
RunSimpleJob(job);
@@ -854,7 +855,8 @@ void ImportDialog::StartBatchBuildingCIA() {
this, importer, std::move(to_import),
[path](Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier,
const Common::ProgressCallback& callback) {
return importer.BuildCIA(specifier, path.toStdString(), callback, true);
return importer.BuildCIA(Core::CIABuildType::Standard, specifier, path.toStdString(),
callback, true);
},
&Core::SDMCImporter::AbortBuildCIA);
RunMultiJob(job, total_count, total_size);