mirror of
https://github.com/jakcron/nstool
synced 2024-12-25 14:11:14 +00:00
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:
parent
978899780c
commit
e205cb6a4f
5 changed files with 126 additions and 32 deletions
|
@ -12,19 +12,24 @@
|
|||
#include <pietendo/hac/es/CertificateBody.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())
|
||||
{
|
||||
importBaseKeyFile(keyfile_path.get(), isDev);
|
||||
}
|
||||
if (titlekeyfile_path.isSet())
|
||||
{
|
||||
importTitleKeyFile(titlekeyfile_path.get());
|
||||
}
|
||||
if (cert_path.isSet())
|
||||
{
|
||||
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.
|
||||
|
@ -447,7 +452,39 @@ void nstool::KeyBagInitializer::importBaseKeyFile(const tc::io::Path& keyfile_pa
|
|||
|
||||
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)
|
||||
|
@ -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());
|
||||
|
||||
// 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())
|
||||
{
|
||||
fallback_enc_content_key = enc_title_key;
|
||||
}
|
||||
external_enc_content_keys[rights_id] = enc_title_key;
|
||||
|
||||
// determine key to decrypt title key
|
||||
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;
|
||||
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;
|
||||
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ struct KeyBag
|
|||
|
||||
// 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_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_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
|
||||
{
|
||||
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:
|
||||
KeyBagInitializer();
|
||||
|
||||
|
|
|
@ -176,6 +176,15 @@ void nstool::NcaProcess::generateNcaBodyEncryptionKeys()
|
|||
{
|
||||
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())
|
||||
{
|
||||
tmp_key = mKeyCfg.fallback_enc_content_key.get();
|
||||
|
|
|
@ -260,6 +260,40 @@ private:
|
|||
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
|
||||
{
|
||||
public:
|
||||
|
@ -504,7 +538,7 @@ nstool::SettingsInitializer::SettingsInitializer(const std::vector<std::string>&
|
|||
mVerbose(false),
|
||||
mNcaEncryptedContentKey(),
|
||||
mNcaContentKey(),
|
||||
mTikPath(),
|
||||
mTikPathList(),
|
||||
mCertPath()
|
||||
{
|
||||
// parse input arguments
|
||||
|
@ -532,29 +566,16 @@ nstool::SettingsInitializer::SettingsInitializer(const std::vector<std::string>&
|
|||
// locate key file, if not specfied
|
||||
if (mKeysetPath.isNull())
|
||||
{
|
||||
std::string home_path_str;
|
||||
if (tc::os::getEnvVar("HOME", home_path_str) || tc::os::getEnvVar("USERPROFILE", home_path_str))
|
||||
{
|
||||
tc::io::Path keyfile_path = tc::io::Path(home_path_str);
|
||||
keyfile_path.push_back(".switch");
|
||||
keyfile_path.push_back(opt.is_dev ? "dev.keys" : "prod.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");
|
||||
}
|
||||
loadKeyFile(mKeysetPath, opt.is_dev ? "dev.keys" : "prod.keys", "Maybe specify it with \"-k <path>\"?\n");
|
||||
}
|
||||
// locate title key file, if not specfied
|
||||
if (mTitleKeysetPath.isNull())
|
||||
{
|
||||
loadKeyFile(mTitleKeysetPath, "title.keys", "");
|
||||
}
|
||||
|
||||
// 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_content_key = mNcaContentKey;
|
||||
|
||||
|
@ -618,9 +639,10 @@ void nstool::SettingsInitializer::parse_args(const std::vector<std::string>& arg
|
|||
|
||||
// get user-provided keydata
|
||||
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(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"})));
|
||||
|
||||
// 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
|
||||
{
|
||||
|
|
|
@ -136,11 +136,15 @@ private:
|
|||
bool mVerbose;
|
||||
|
||||
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> 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;
|
||||
|
||||
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 determineValidEsCertFromSample(const tc::ByteData& raw_data) const;
|
||||
bool determineValidEsTikFromSample(const tc::ByteData& raw_data) const;
|
||||
|
|
Loading…
Reference in a new issue