Change --tik so that it can be invoked multiple times to specify a list of ticket files. Additionally added support for hactool title.keys

This commit is contained in:
Jack 2024-01-01 13:22:39 +08:00
parent 978899780c
commit e205cb6a4f
5 changed files with 126 additions and 32 deletions

View file

@ -12,19 +12,24 @@
#include <pietendo/hac/es/CertificateBody.h> #include <pietendo/hac/es/CertificateBody.h>
#include <pietendo/hac/es/TicketBody_V2.h> #include <pietendo/hac/es/TicketBody_V2.h>
nstool::KeyBagInitializer::KeyBagInitializer(bool isDev, const tc::Optional<tc::io::Path>& keyfile_path, const tc::Optional<tc::io::Path>& tik_path, const tc::Optional<tc::io::Path>& cert_path) nstool::KeyBagInitializer::KeyBagInitializer(bool isDev, const tc::Optional<tc::io::Path>& keyfile_path, const tc::Optional<tc::io::Path>& titlekeyfile_path, const std::vector<tc::io::Path>& tik_path_list, const tc::Optional<tc::io::Path>& cert_path)
{ {
if (keyfile_path.isSet()) if (keyfile_path.isSet())
{ {
importBaseKeyFile(keyfile_path.get(), isDev); importBaseKeyFile(keyfile_path.get(), isDev);
} }
if (titlekeyfile_path.isSet())
{
importTitleKeyFile(titlekeyfile_path.get());
}
if (cert_path.isSet()) if (cert_path.isSet())
{ {
importCertificateChain(cert_path.get()); importCertificateChain(cert_path.get());
} }
if (tik_path.isSet()) if (!tik_path_list.empty())
{ {
importTicket(tik_path.get()); for (auto itr = tik_path_list.begin(); itr != tik_path_list.end(); itr++)
importTicket(*itr);
} }
// this will populate known keys if they aren't supplied by the user provided keyfiles. // this will populate known keys if they aren't supplied by the user provided keyfiles.
@ -447,7 +452,39 @@ void nstool::KeyBagInitializer::importBaseKeyFile(const tc::io::Path& keyfile_pa
void nstool::KeyBagInitializer::importTitleKeyFile(const tc::io::Path& keyfile_path) void nstool::KeyBagInitializer::importTitleKeyFile(const tc::io::Path& keyfile_path)
{ {
std::shared_ptr<tc::io::FileStream> keyfile_stream = std::make_shared<tc::io::FileStream>(tc::io::FileStream(keyfile_path, tc::io::FileMode::Open, tc::io::FileAccess::Read));
// import keyfile into a dictionary
std::map<std::string, std::string> keyfile_dict;
processResFile(keyfile_stream, keyfile_dict);
// process title keys
tc::ByteData tmp;
KeyBag::rights_id_t rights_id_tmp;
KeyBag::aes128_key_t title_key_tmp;
for (auto itr = keyfile_dict.begin(); itr != keyfile_dict.end(); itr++)
{
//fmt::print("RightsID[{:s}] = TitleKey[{:s}]\n", itr->first, itr->second);
// parse the rights id
tmp = tc::cli::FormatUtil::hexStringToBytes(itr->first);
if (tmp.size() != rights_id_tmp.size())
{
throw tc::ArgumentException("nstool::KeyBagInitializer", "RightsID: \"" + itr->first + "\" has incorrect length");
}
memcpy(rights_id_tmp.data(), tmp.data(), rights_id_tmp.size());
// parse the title key
tmp = tc::cli::FormatUtil::hexStringToBytes(itr->second);
if (tmp.size() != title_key_tmp.size())
{
throw tc::ArgumentException("nstool::KeyBagInitializer", "TitleKey for \""+ itr->first + "\": \"" + itr->second + "\" has incorrect length");
}
memcpy(title_key_tmp.data(), tmp.data(), title_key_tmp.size());
// save to encrypted key dict
external_enc_content_keys[rights_id_tmp] = title_key_tmp;
}
} }
void nstool::KeyBagInitializer::importCertificateChain(const tc::io::Path& cert_path) void nstool::KeyBagInitializer::importCertificateChain(const tc::io::Path& cert_path)
@ -551,10 +588,7 @@ void nstool::KeyBagInitializer::importTicket(const tc::io::Path& tik_path)
memcpy(enc_title_key.data(), tik.getBody().getEncTitleKey(), enc_title_key.size()); memcpy(enc_title_key.data(), tik.getBody().getEncTitleKey(), enc_title_key.size());
// save the encrypted title key as the fallback enc content key incase the ticket was malformed and workarounds to decrypt it in isolation fail // save the encrypted title key as the fallback enc content key incase the ticket was malformed and workarounds to decrypt it in isolation fail
if (fallback_enc_content_key.isNull()) external_enc_content_keys[rights_id] = enc_title_key;
{
fallback_enc_content_key = enc_title_key;
}
// determine key to decrypt title key // determine key to decrypt title key
byte_t common_key_index = tik.getBody().getCommonKeyId(); byte_t common_key_index = tik.getBody().getCommonKeyId();
@ -581,7 +615,7 @@ void nstool::KeyBagInitializer::importTicket(const tc::io::Path& tik_path)
aes128_key_t dec_title_key; aes128_key_t dec_title_key;
tc::crypto::DecryptAes128Ecb(dec_title_key.data(), enc_title_key.data(), sizeof(aes128_key_t), etik_common_key[common_key_index].data(), sizeof(aes128_key_t)); tc::crypto::DecryptAes128Ecb(dec_title_key.data(), enc_title_key.data(), sizeof(aes128_key_t), etik_common_key[common_key_index].data(), sizeof(aes128_key_t));
// add to key dict // add to decrypted key dict
external_content_keys[rights_id] = dec_title_key; external_content_keys[rights_id] = dec_title_key;
} }

View file

@ -39,6 +39,7 @@ struct KeyBag
// external content keys (nca<->ticket) // external content keys (nca<->ticket)
std::map<rights_id_t, aes128_key_t> external_content_keys; std::map<rights_id_t, aes128_key_t> external_content_keys;
std::map<rights_id_t, aes128_key_t> external_enc_content_keys; // encrypted content key list to be used when external_content_keys does not have the required content key (usually taken raw from ticket)
tc::Optional<aes128_key_t> fallback_enc_content_key; // encrypted content key to be used when external_content_keys does not have the required content key (usually taken raw from ticket) tc::Optional<aes128_key_t> fallback_enc_content_key; // encrypted content key to be used when external_content_keys does not have the required content key (usually taken raw from ticket)
tc::Optional<aes128_key_t> fallback_content_key; // content key to be used when external_content_keys does not have the required content key (usually already decrypted from ticket) tc::Optional<aes128_key_t> fallback_content_key; // content key to be used when external_content_keys does not have the required content key (usually already decrypted from ticket)
@ -70,7 +71,7 @@ struct KeyBag
class KeyBagInitializer : public KeyBag class KeyBagInitializer : public KeyBag
{ {
public: public:
KeyBagInitializer(bool isDev, const tc::Optional<tc::io::Path>& keyfile_path, const tc::Optional<tc::io::Path>& tik_path, const tc::Optional<tc::io::Path>& cert_path); KeyBagInitializer(bool isDev, const tc::Optional<tc::io::Path>& keyfile_path, const tc::Optional<tc::io::Path>& titlekeyfile_path, const std::vector<tc::io::Path>& tik_path_list, const tc::Optional<tc::io::Path>& cert_path);
private: private:
KeyBagInitializer(); KeyBagInitializer();

View file

@ -176,6 +176,15 @@ void nstool::NcaProcess::generateNcaBodyEncryptionKeys()
{ {
mContentKey.aes_ctr = mKeyCfg.fallback_content_key.get(); mContentKey.aes_ctr = mKeyCfg.fallback_content_key.get();
} }
else if (mKeyCfg.external_enc_content_keys.find(mHdr.getRightsId()) != mKeyCfg.external_enc_content_keys.end())
{
tmp_key = mKeyCfg.external_enc_content_keys[mHdr.getRightsId()];
if (mKeyCfg.etik_common_key.find(masterkey_rev) != mKeyCfg.etik_common_key.end())
{
pie::hac::AesKeygen::generateKey(tmp_key.data(), tmp_key.data(), mKeyCfg.etik_common_key[masterkey_rev].data());
mContentKey.aes_ctr = tmp_key;
}
}
else if (mKeyCfg.fallback_enc_content_key.isSet()) else if (mKeyCfg.fallback_enc_content_key.isSet())
{ {
tmp_key = mKeyCfg.fallback_enc_content_key.get(); tmp_key = mKeyCfg.fallback_enc_content_key.get();

View file

@ -260,6 +260,40 @@ private:
std::vector<std::string> mOptRegex; std::vector<std::string> mOptRegex;
}; };
class SingleParamPathArrayOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
SingleParamPathArrayOptionHandler(std::vector<tc::io::Path>& param, const std::vector<std::string>& opts) :
mParam(param),
mOptStrings(opts),
mOptRegex()
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
const std::vector<std::string>& getOptionRegexPatterns() const
{
return mOptRegex;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
if (params.size() != 1)
{
throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option));
}
mParam.push_back(params[0]);
}
private:
std::vector<tc::io::Path>& mParam;
std::vector<std::string> mOptStrings;
std::vector<std::string> mOptRegex;
};
class FileTypeOptionHandler : public tc::cli::OptionParser::IOptionHandler class FileTypeOptionHandler : public tc::cli::OptionParser::IOptionHandler
{ {
public: public:
@ -504,7 +538,7 @@ nstool::SettingsInitializer::SettingsInitializer(const std::vector<std::string>&
mVerbose(false), mVerbose(false),
mNcaEncryptedContentKey(), mNcaEncryptedContentKey(),
mNcaContentKey(), mNcaContentKey(),
mTikPath(), mTikPathList(),
mCertPath() mCertPath()
{ {
// parse input arguments // parse input arguments
@ -532,29 +566,16 @@ nstool::SettingsInitializer::SettingsInitializer(const std::vector<std::string>&
// locate key file, if not specfied // locate key file, if not specfied
if (mKeysetPath.isNull()) if (mKeysetPath.isNull())
{ {
std::string home_path_str; loadKeyFile(mKeysetPath, opt.is_dev ? "dev.keys" : "prod.keys", "Maybe specify it with \"-k <path>\"?\n");
if (tc::os::getEnvVar("HOME", home_path_str) || tc::os::getEnvVar("USERPROFILE", home_path_str)) }
{ // locate title key file, if not specfied
tc::io::Path keyfile_path = tc::io::Path(home_path_str); if (mTitleKeysetPath.isNull())
keyfile_path.push_back(".switch"); {
keyfile_path.push_back(opt.is_dev ? "dev.keys" : "prod.keys"); loadKeyFile(mTitleKeysetPath, "title.keys", "");
try {
tc::io::FileStream test = tc::io::FileStream(keyfile_path, tc::io::FileMode::Open, tc::io::FileAccess::Read);
mKeysetPath = keyfile_path;
}
catch (tc::io::FileNotFoundException&) {
fmt::print("[WARNING] Failed to load \"{}\" keyfile. Maybe specify it with \"-k <path>\"?\n", opt.is_dev ? "dev.keys" : "prod.keys");
}
}
else {
fmt::print("[WARNING] Failed to located \"{}\" keyfile. Maybe specify it with \"-k <path>\"?\n", opt.is_dev ? "dev.keys" : "prod.keys");
}
} }
// generate keybag // generate keybag
opt.keybag = KeyBagInitializer(opt.is_dev, mKeysetPath, mTikPath, mCertPath); opt.keybag = KeyBagInitializer(opt.is_dev, mKeysetPath, mTitleKeysetPath, mTikPathList, mCertPath);
opt.keybag.fallback_enc_content_key = mNcaEncryptedContentKey; opt.keybag.fallback_enc_content_key = mNcaEncryptedContentKey;
opt.keybag.fallback_content_key = mNcaContentKey; opt.keybag.fallback_content_key = mNcaContentKey;
@ -618,9 +639,10 @@ void nstool::SettingsInitializer::parse_args(const std::vector<std::string>& arg
// get user-provided keydata // get user-provided keydata
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(mKeysetPath, {"-k", "--keyset"}))); opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(mKeysetPath, {"-k", "--keyset"})));
//opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(mTitleKeysetPath, {"--titlekeyset"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamAesKeyOptionHandler>(new SingleParamAesKeyOptionHandler(mNcaEncryptedContentKey, {"--titlekey"}))); opts.registerOptionHandler(std::shared_ptr<SingleParamAesKeyOptionHandler>(new SingleParamAesKeyOptionHandler(mNcaEncryptedContentKey, {"--titlekey"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamAesKeyOptionHandler>(new SingleParamAesKeyOptionHandler(mNcaContentKey, {"--contentkey", "--bodykey"}))); opts.registerOptionHandler(std::shared_ptr<SingleParamAesKeyOptionHandler>(new SingleParamAesKeyOptionHandler(mNcaContentKey, {"--contentkey", "--bodykey"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(mTikPath, {"--tik"}))); opts.registerOptionHandler(std::shared_ptr<SingleParamPathArrayOptionHandler>(new SingleParamPathArrayOptionHandler(mTikPathList, {"--tik"})));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(mCertPath, {"--cert"}))); opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(mCertPath, {"--cert"})));
// code options // code options
@ -967,6 +989,30 @@ void nstool::SettingsInitializer::dump_rsa_key(const KeyBag::rsa_key_t& key, con
} }
} }
void nstool::SettingsInitializer::loadKeyFile(tc::Optional<tc::io::Path>& keyfile_path, const std::string& keyfile_name, const std::string& cli_hint)
{
std::string home_path_str;
if (tc::os::getEnvVar("HOME", home_path_str) || tc::os::getEnvVar("USERPROFILE", home_path_str))
{
tc::io::Path tmp_path = tc::io::Path(home_path_str);
tmp_path.push_back(".switch");
tmp_path.push_back(keyfile_name);
try {
tc::io::FileStream test = tc::io::FileStream(tmp_path, tc::io::FileMode::Open, tc::io::FileAccess::Read);
keyfile_path = tmp_path;
}
catch (tc::io::FileNotFoundException&) {
fmt::print("[WARNING] Failed to load \"{}\" keyfile.{}\n", keyfile_name, cli_hint);
}
}
else {
fmt::print("[WARNING] Failed to locate \"{}\" keyfile.{}\n", keyfile_name, cli_hint);
}
}
bool nstool::SettingsInitializer::determineValidNcaFromSample(const tc::ByteData& sample) const bool nstool::SettingsInitializer::determineValidNcaFromSample(const tc::ByteData& sample) const
{ {

View file

@ -136,11 +136,15 @@ private:
bool mVerbose; bool mVerbose;
tc::Optional<tc::io::Path> mKeysetPath; tc::Optional<tc::io::Path> mKeysetPath;
tc::Optional<tc::io::Path> mTitleKeysetPath;
tc::Optional<KeyBag::aes128_key_t> mNcaEncryptedContentKey; tc::Optional<KeyBag::aes128_key_t> mNcaEncryptedContentKey;
tc::Optional<KeyBag::aes128_key_t> mNcaContentKey; tc::Optional<KeyBag::aes128_key_t> mNcaContentKey;
tc::Optional<tc::io::Path> mTikPath; std::vector<tc::io::Path> mTikPathList;
//tc::Optional<tc::io::Path> mTikPath;
tc::Optional<tc::io::Path> mCertPath; tc::Optional<tc::io::Path> mCertPath;
void loadKeyFile(tc::Optional<tc::io::Path>& keyfile_path, const std::string& keyfile_name, const std::string& cli_hint);
bool determineValidNcaFromSample(const tc::ByteData& raw_data) const; bool determineValidNcaFromSample(const tc::ByteData& raw_data) const;
bool determineValidEsCertFromSample(const tc::ByteData& raw_data) const; bool determineValidEsCertFromSample(const tc::ByteData& raw_data) const;
bool determineValidEsTikFromSample(const tc::ByteData& raw_data) const; bool determineValidEsTikFromSample(const tc::ByteData& raw_data) const;