mirror of
https://github.com/jakcron/nstool
synced 2024-11-15 02:06:40 +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/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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
if (mTitleKeysetPath.isNull())
|
||||||
{
|
{
|
||||||
tc::io::Path keyfile_path = tc::io::Path(home_path_str);
|
loadKeyFile(mTitleKeysetPath, "title.keys", "");
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue