nstool/src/EsTikProcess.cpp
2021-10-03 12:01:23 +08:00

276 lines
No EOL
7.6 KiB
C++

#include "EsTikProcess.h"
#include "PkiValidator.h"
#include <nn/pki/SignUtils.h>
nstool::EsTikProcess::EsTikProcess() :
mModuleName("nstool::EsTikProcess"),
mFile(),
mCliOutputMode(true, false, false, false),
mVerify(false)
{
}
void nstool::EsTikProcess::process()
{
importTicket();
if (mVerify)
verifyTicket();
if (mCliOutputMode.show_basic_info)
displayTicket();
}
void nstool::EsTikProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void nstool::EsTikProcess::setKeyCfg(const KeyBag& keycfg)
{
mKeyCfg = keycfg;
}
void nstool::EsTikProcess::setCertificateChain(const std::vector<nn::pki::SignedData<nn::pki::CertificateBody>>& certs)
{
mCerts = certs;
}
void nstool::EsTikProcess::setCliOutputMode(CliOutputMode mode)
{
mCliOutputMode = mode;
}
void nstool::EsTikProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void nstool::EsTikProcess::importTicket()
{
if (mFile == nullptr)
{
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
// check if file_size is greater than 20MB, don't import.
size_t file_size = tc::io::IOUtil::castInt64ToSize(mFile->length());
if (file_size > (0x100000 * 20))
{
throw tc::Exception(mModuleName, "File too large.");
}
// read cnmt
tc::ByteData scratch = tc::ByteData(file_size);
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
mTik.fromBytes(scratch.data(), scratch.size());
}
void nstool::EsTikProcess::verifyTicket()
{
PkiValidator pki_validator;
tc::ByteData tik_hash;
switch (nn::pki::sign::getHashAlgo(mTik.getSignature().getSignType()))
{
case (nn::pki::sign::HASH_ALGO_SHA1):
tik_hash = tc::ByteData(tc::crypto::Sha1Generator::kHashSize);
tc::crypto::GenerateSha1Hash(tik_hash.data(), mTik.getBody().getBytes().data(), mTik.getBody().getBytes().size());
break;
case (nn::pki::sign::HASH_ALGO_SHA256):
tik_hash = tc::ByteData(tc::crypto::Sha256Generator::kHashSize);
tc::crypto::GenerateSha256Hash(tik_hash.data(), mTik.getBody().getBytes().data(), mTik.getBody().getBytes().size());
break;
}
try
{
pki_validator.setKeyCfg(mKeyCfg);
pki_validator.addCertificates(mCerts);
pki_validator.validateSignature(mTik.getBody().getIssuer(), mTik.getSignature().getSignType(), mTik.getSignature().getSignature(), tik_hash);
}
catch (const tc::Exception& e)
{
fmt::print("[WARNING] Ticket signature could not be validated ({:s})\n", e.error());
}
}
void nstool::EsTikProcess::displayTicket()
{
const nn::es::TicketBody_V2& body = mTik.getBody();
fmt::print("[ES Ticket]\n");
fmt::print(" SignType: {:s}", getSignTypeStr(mTik.getSignature().getSignType()));
if (mCliOutputMode.show_extended_info)
fmt::print(" (0x{:x})", mTik.getSignature().getSignType());
fmt::print("\n");
fmt::print(" Issuer: {:s}\n", body.getIssuer());
fmt::print(" Title Key:\n");
fmt::print(" EncMode: {:s}\n", getTitleKeyPersonalisationStr(body.getTitleKeyEncType()));
fmt::print(" KeyGeneration: {:d}\n", (uint32_t)body.getCommonKeyId());
if (body.getTitleKeyEncType() == nn::es::ticket::RSA2048)
{
fmt::print(" Data:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(body.getEncTitleKey(), 0x100, true, ":", 0x10, 6, false));
}
else if (body.getTitleKeyEncType() == nn::es::ticket::AES128_CBC)
{
fmt::print(" Data:\n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(body.getEncTitleKey(), 0x10, true, ":"));
}
else
{
fmt::print(" Data: <cannot display>\n");
}
fmt::print(" Version: {:s} (v{:d})\n", getTitleVersionStr(body.getTicketVersion()), body.getTicketVersion());
fmt::print(" License Type: {:s}\n", getLicenseTypeStr(body.getLicenseType()));
if (body.getPropertyFlags().size() > 0)
{
nn::es::sTicketBody_v2* raw_body = (nn::es::sTicketBody_v2*)body.getBytes().data();
fmt::print(" PropertyMask: 0x{:04x}\n", ((tc::bn::le16<uint16_t>*)&raw_body->property_mask)->unwrap());
for (size_t i = 0; i < body.getPropertyFlags().size(); i++)
{
fmt::print(" {:s}\n", getPropertyFlagStr(body.getPropertyFlags()[i]));
}
}
if (mCliOutputMode.show_extended_info)
{
fmt::print(" Reserved Region:\n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(body.getReservedRegion(), 8, true, ""));
}
if (body.getTicketId() != 0 || mCliOutputMode.show_extended_info)
fmt::print(" TicketId: 0x{:016x}\n", body.getTicketId());
if (body.getDeviceId() != 0 || mCliOutputMode.show_extended_info)
fmt::print(" DeviceId: 0x{:016x}\n", body.getDeviceId());
fmt::print(" RightsId: \n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(body.getRightsId(), 16, true, ""));
fmt::print(" SectionTotalSize: 0x{:x}\n", body.getSectionTotalSize());
fmt::print(" SectionHeaderOffset: 0x{:x}\n", body.getSectionHeaderOffset());
fmt::print(" SectionNum: 0x{:x}\n", body.getSectionNum());
fmt::print(" SectionEntrySize: 0x{:x}\n", body.getSectionEntrySize());
}
std::string nstool::EsTikProcess::getSignTypeStr(uint32_t type) const
{
std::string str;
switch(type)
{
case (nn::pki::sign::SIGN_ID_RSA4096_SHA1):
str = "RSA4096-SHA1";
break;
case (nn::pki::sign::SIGN_ID_RSA2048_SHA1):
str = "RSA2048-SHA1";
break;
case (nn::pki::sign::SIGN_ID_ECDSA240_SHA1):
str = "ECDSA240-SHA1";
break;
case (nn::pki::sign::SIGN_ID_RSA4096_SHA256):
str = "RSA4096-SHA256";
break;
case (nn::pki::sign::SIGN_ID_RSA2048_SHA256):
str = "RSA2048-SHA256";
break;
case (nn::pki::sign::SIGN_ID_ECDSA240_SHA256):
str = "ECDSA240-SHA256";
break;
default:
str = "Unknown";
break;
}
return str;
}
std::string nstool::EsTikProcess::getTitleKeyPersonalisationStr(byte_t flag) const
{
std::string str;
switch(flag)
{
case (nn::es::ticket::AES128_CBC):
str = "Generic (AESCBC)";
break;
case (nn::es::ticket::RSA2048):
str = "Personalised (RSA2048)";
break;
default:
str = fmt::format("Unknown ({:d})", flag);
break;
}
return str;
}
std::string nstool::EsTikProcess::getLicenseTypeStr(byte_t flag) const
{
std::string str;
switch(flag)
{
case (nn::es::ticket::LICENSE_PERMANENT):
str = "Permanent";
break;
case (nn::es::ticket::LICENSE_DEMO):
str = "Demo";
break;
case (nn::es::ticket::LICENSE_TRIAL):
str = "Trial";
break;
case (nn::es::ticket::LICENSE_RENTAL):
str = "Rental";
break;
case (nn::es::ticket::LICENSE_SUBSCRIPTION):
str = "Subscription";
break;
case (nn::es::ticket::LICENSE_SERVICE):
str = "Service";
break;
default:
str = fmt::format("Unknown ({:d})", flag);
break;
}
return str;
}
std::string nstool::EsTikProcess::getPropertyFlagStr(byte_t flag) const
{
std::string str;
switch(flag)
{
case (nn::es::ticket::FLAG_PRE_INSTALL):
str = "PreInstall";
break;
case (nn::es::ticket::FLAG_SHARED_TITLE):
str = "SharedTitle";
break;
case (nn::es::ticket::FLAG_ALLOW_ALL_CONTENT):
str = "AllContent";
break;
case (nn::es::ticket::FLAG_DEVICE_LINK_INDEPENDENT):
str = "DeviceLinkIndependent";
break;
case (nn::es::ticket::FLAG_VOLATILE):
str = "Volatile";
break;
case (nn::es::ticket::FLAG_ELICENSE_REQUIRED):
str = "ELicenseRequired";
break;
default:
str = fmt::format("Unknown ({:d})", flag);
break;
}
return str;
}
std::string nstool::EsTikProcess::getTitleVersionStr(uint16_t version) const
{
return fmt::format("{:d}.{:d}.{:d}", ((version>>10) & 0x3f), ((version>>4) & 0x3f), ((version>>0) & 0xf));
}