diff --git a/lib/libfnd/include/fnd/SimpleTextOutput.h b/lib/libfnd/include/fnd/SimpleTextOutput.h index ef0516f..15c3c8e 100644 --- a/lib/libfnd/include/fnd/SimpleTextOutput.h +++ b/lib/libfnd/include/fnd/SimpleTextOutput.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace fnd { @@ -12,6 +13,8 @@ namespace fnd static void hexDump(const byte_t* data, size_t len, size_t row_len, size_t indent_len); static void hexDump(const byte_t* data, size_t len); static std::string arrayToString(const byte_t* data, size_t len, bool upper_case, const std::string& separator); + static void stringToArray(const std::string& str, fnd::Vec& array); + private: static const size_t kDefaultRowLen = 0x10; static const size_t kDefaultByteGroupingSize = 1; diff --git a/lib/libfnd/include/fnd/ecdsa.h b/lib/libfnd/include/fnd/ecdsa.h index 04cfcff..aa9e48b 100644 --- a/lib/libfnd/include/fnd/ecdsa.h +++ b/lib/libfnd/include/fnd/ecdsa.h @@ -43,7 +43,7 @@ namespace fnd void operator=(const sEcdsa240PrivateKey& other) { - memcpy(this->k, k, kEcdsa240Size); + memcpy(this->k, other.k, kEcdsa240Size); } bool operator==(const sEcdsa240PrivateKey& other) const @@ -56,6 +56,28 @@ namespace fnd return !operator==(other); } }; + + struct sEcdsa240Key + { + sEcdsa240Point pub; + sEcdsa240PrivateKey pvt; + + void operator=(const sEcdsa240Key& other) + { + this->pub = other.pub; + this->pvt = other.pvt; + } + + bool operator==(const sEcdsa240Key& other) const + { + return this->pub == other.pub && this->pvt == other.pvt; + } + + bool operator!=(const sEcdsa240Key& other) const + { + return !operator==(other); + } + }; #pragma pack (pop) } } diff --git a/lib/libfnd/source/SimpleTextOutput.cpp b/lib/libfnd/source/SimpleTextOutput.cpp index c7f1582..1ee71ce 100644 --- a/lib/libfnd/source/SimpleTextOutput.cpp +++ b/lib/libfnd/source/SimpleTextOutput.cpp @@ -87,4 +87,31 @@ std::string fnd::SimpleTextOutput::arrayToString(const byte_t* data, size_t len, ss << separator; } return ss.str(); +} + +inline byte_t charToByte(char chr) +{ + if (chr >= 'a' && chr <= 'f') + return (chr - 'a') + 0xa; + else if (chr >= 'A' && chr <= 'F') + return (chr - 'A') + 0xa; + else if (chr >= '0' && chr <= '9') + return chr - '0'; + return 0; +} + +void fnd::SimpleTextOutput::stringToArray(const std::string& str, fnd::Vec& array) +{ + size_t size = str.size(); + if ((size % 2)) + { + return; + } + + array.alloc(size/2); + + for (size_t i = 0; i < array.size(); i++) + { + array[i] = (charToByte(str[i * 2]) << 4) | charToByte(str[(i * 2) + 1]); + } } \ No newline at end of file diff --git a/programs/nstool/source/EsTikProcess.cpp b/programs/nstool/source/EsTikProcess.cpp index 5a3801a..5bae731 100644 --- a/programs/nstool/source/EsTikProcess.cpp +++ b/programs/nstool/source/EsTikProcess.cpp @@ -41,9 +41,9 @@ void EsTikProcess::setInputFile(fnd::IFile* file, bool ownIFile) mOwnIFile = ownIFile; } -void EsTikProcess::setKeyset(const sKeyset* keyset) +void EsTikProcess::setKeyCfg(const KeyConfiguration& keycfg) { - mKeyset = keyset; + mKeyCfg = keycfg; } void EsTikProcess::setCertificateChain(const fnd::List>& certs) @@ -95,7 +95,7 @@ void EsTikProcess::verifyTicket() try { - pki_validator.setRootKey(mKeyset->pki.root_sign_key); + pki_validator.setKeyCfg(mKeyCfg); pki_validator.addCertificates(mCerts); pki_validator.validateSignature(mTik.getBody().getIssuer(), mTik.getSignature().getSignType(), mTik.getSignature().getSignature(), tik_hash); } diff --git a/programs/nstool/source/EsTikProcess.h b/programs/nstool/source/EsTikProcess.h index 82d82d0..ae77ec3 100644 --- a/programs/nstool/source/EsTikProcess.h +++ b/programs/nstool/source/EsTikProcess.h @@ -6,6 +6,7 @@ #include #include #include +#include "KeyConfiguration.h" #include "nstool.h" class EsTikProcess @@ -17,7 +18,7 @@ public: void process(); void setInputFile(fnd::IFile* file, bool ownIFile); - void setKeyset(const sKeyset* keyset); + void setKeyCfg(const KeyConfiguration& keycfg); void setCertificateChain(const fnd::List>& certs); void setCliOutputMode(CliOutputMode mode); void setVerifyMode(bool verify); @@ -27,7 +28,7 @@ private: fnd::IFile* mFile; bool mOwnIFile; - const sKeyset* mKeyset; + KeyConfiguration mKeyCfg; CliOutputMode mCliOutputMode; bool mVerify; diff --git a/programs/nstool/source/KeyConfiguration.cpp b/programs/nstool/source/KeyConfiguration.cpp new file mode 100644 index 0000000..91c1122 --- /dev/null +++ b/programs/nstool/source/KeyConfiguration.cpp @@ -0,0 +1,376 @@ +#include "KeyConfiguration.h" +#include +#include +#include +#include + +KeyConfiguration::KeyConfiguration() +{ + clearGeneralKeyConfiguration(); + clearNcaExternalKeys(); +} + +KeyConfiguration::KeyConfiguration(const KeyConfiguration& other) +{ + *this = other; +} + +void KeyConfiguration::operator=(const KeyConfiguration& other) +{ + mAcidSignKey = other.mAcidSignKey; + mPkg2SignKey = other.mPkg2SignKey; + mNcaHeader0SignKey = other.mNcaHeader0SignKey; + mXciHeaderSignKey = other.mXciHeaderSignKey; + + mNcaHeaderKey = other.mNcaHeaderKey; + mXciHeaderKey = other.mXciHeaderKey; + + for (size_t i = 0; i < kMasterKeyNum; i++) + { + mPkg2Key[i] = other.mPkg2Key[i]; + mPkg1Key[i] = other.mPkg1Key[i]; + mNcaKeyAreaEncryptionKey[0][i] = other.mNcaKeyAreaEncryptionKey[0][i]; + mNcaKeyAreaEncryptionKey[1][i] = other.mNcaKeyAreaEncryptionKey[1][i]; + mNcaKeyAreaEncryptionKey[2][i] = other.mNcaKeyAreaEncryptionKey[2][i]; + mNcaKeyAreaEncryptionKeyHw[0][i] = other.mNcaKeyAreaEncryptionKeyHw[0][i]; + mNcaKeyAreaEncryptionKeyHw[1][i] = other.mNcaKeyAreaEncryptionKeyHw[1][i]; + mNcaKeyAreaEncryptionKeyHw[2][i] = other.mNcaKeyAreaEncryptionKeyHw[2][i]; + mETicketCommonKey[i] = other.mETicketCommonKey[i]; + } + + mPkiRootKeyList = other.mPkiRootKeyList; + + mNcaExternalContentKeyList = other.mNcaExternalContentKeyList; +} + +void KeyConfiguration::importHactoolGenericKeyfile(const std::string& path) +{ + clearGeneralKeyConfiguration(); + + fnd::ResourceFileReader res; + try + { + res.processFile(path); + } + catch (const fnd::Exception&) + { + throw fnd::Exception(kModuleName, "Failed to open key file: " + path); + } + + // internally used sources + fnd::aes::sAes128Key master_key[kMasterKeyNum] = { kNullAesKey }; + fnd::aes::sAes128Key package2_key_source = kNullAesKey; + fnd::aes::sAes128Key ticket_titlekek_source = kNullAesKey; + fnd::aes::sAes128Key key_area_key_source[kNcaKeakNum] = { kNullAesKey, kNullAesKey, kNullAesKey }; + fnd::aes::sAes128Key aes_kek_generation_source = kNullAesKey; + fnd::aes::sAes128Key aes_key_generation_source = kNullAesKey; + fnd::aes::sAes128Key nca_header_kek_source = kNullAesKey; + fnd::aes::sAesXts128Key nca_header_key_source = kNullAesXtsKey; + fnd::rsa::sRsa4096Key pki_root_sign_key = kNullRsa4096Key; + +#define _CONCAT_2_STRINGS(str1, str2) ((str1) + "_" + (str2)) +#define _CONCAT_3_STRINGS(str1, str2, str3) _CONCAT_2_STRINGS(_CONCAT_2_STRINGS(str1, str2), str3) + + std::string key,val; + fnd::Vec dec_array; + +#define _SAVE_KEYDATA(key_name, array, len) \ + key = (key_name); \ + val = res[key]; \ + if (val.empty() == false) { \ + fnd::SimpleTextOutput::stringToArray(val, dec_array); \ + if (dec_array.size() != len) \ + throw fnd::Exception(kModuleName, "Key: \"" + key_name + "\" has incorrect length"); \ + memcpy(array, dec_array.data(), len); \ + } + + for (size_t nameidx = 0; nameidx < kNameVariantNum; nameidx++) + { + // import sources + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kPkg2Base[nameidx], kKeyStr, kSourceStr), package2_key_source.key, 0x10); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[nameidx], kSourceStr), ticket_titlekek_source.key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[0], kSourceStr), key_area_key_source[0].key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[1], kSourceStr), key_area_key_source[1].key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[2], kSourceStr), key_area_key_source[2].key, 0x10); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kKekGenBase[nameidx], kSourceStr), aes_kek_generation_source.key, 0x10); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kKeyGenBase[nameidx], kSourceStr), aes_key_generation_source.key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kXciHeaderBase[nameidx], kKekStr, kSourceStr), nca_header_kek_source.key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kXciHeaderBase[nameidx], kKeyStr, kSourceStr), nca_header_key_source.key, 0x20); + + // Store Key Variants/Derivatives + for (size_t mkeyidx = 0; mkeyidx < kMasterKeyNum; mkeyidx++) + { + + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kMasterBase[nameidx], kKeyStr, kKeyIndex[mkeyidx]), master_key[mkeyidx].key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kPkg1Base[nameidx], kKeyStr, kKeyIndex[mkeyidx]), mPkg1Key[mkeyidx].key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kPkg2Base[nameidx], kKeyStr, kKeyIndex[mkeyidx]), mPkg2Key[mkeyidx].key, 0x10); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[nameidx], kKeyIndex[mkeyidx]), mETicketCommonKey[mkeyidx].key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[0], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKey[0][mkeyidx].key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[1], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKey[1][mkeyidx].key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[2], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKey[2][mkeyidx].key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyHwBase[nameidx], kNcaKeyAreaKeyIndexStr[0], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKeyHw[0][mkeyidx].key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyHwBase[nameidx], kNcaKeyAreaKeyIndexStr[1], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKeyHw[1][mkeyidx].key, 0x10); + _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyHwBase[nameidx], kNcaKeyAreaKeyIndexStr[2], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKeyHw[2][mkeyidx].key, 0x10); + } + + // store nca header key + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[nameidx], kKeyStr), mNcaHeaderKey.key[0], 0x20); + + // store xci header key + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase[nameidx], kKeyStr), mXciHeaderKey.key, 0x10); + + // store rsa keys + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[nameidx], kRsaKeyPrivate), mNcaHeader0SignKey.priv_exponent, fnd::rsa::kRsa2048Size); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[nameidx], kRsaKeyModulus), mNcaHeader0SignKey.modulus, fnd::rsa::kRsa2048Size); + + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase[nameidx], kRsaKeyPrivate), mXciHeaderSignKey.priv_exponent, fnd::rsa::kRsa2048Size); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase[nameidx], kRsaKeyModulus), mXciHeaderSignKey.modulus, fnd::rsa::kRsa2048Size); + + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kAcidBase[nameidx], kRsaKeyPrivate), mAcidSignKey.priv_exponent, fnd::rsa::kRsa2048Size); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kAcidBase[nameidx], kRsaKeyModulus), mAcidSignKey.modulus, fnd::rsa::kRsa2048Size); + + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPkg2Base[nameidx], kRsaKeyPrivate), mPkg2SignKey.priv_exponent, fnd::rsa::kRsa2048Size); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPkg2Base[nameidx], kRsaKeyModulus), mPkg2SignKey.modulus, fnd::rsa::kRsa2048Size); + + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPkiRootBase[nameidx], kRsaKeyPrivate), pki_root_sign_key.priv_exponent, fnd::rsa::kRsa4096Size); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPkiRootBase[nameidx], kRsaKeyModulus), pki_root_sign_key.modulus, fnd::rsa::kRsa4096Size); + } + +#undef _SAVE_KEYDATA +#undef _CONCAT_3_STRINGS +#undef _CONCAT_2_STRINGS + + // Derive keys + for (size_t i = 0; i < kMasterKeyNum; i++) + { + if (master_key[i] != kNullAesKey) + { + if (aes_kek_generation_source != kNullAesKey && aes_key_generation_source != kNullAesKey) + { + if (i == 0 && nca_header_kek_source != kNullAesKey && nca_header_key_source != kNullAesXtsKey) + { + if (mNcaHeaderKey == kNullAesXtsKey) + { + fnd::aes::sAes128Key nca_header_kek; + nn::hac::AesKeygen::generateKey(nca_header_kek.key, aes_kek_generation_source.key, nca_header_kek_source.key, aes_key_generation_source.key, master_key[i].key); + nn::hac::AesKeygen::generateKey(mNcaHeaderKey.key[0], nca_header_key_source.key[0], nca_header_kek.key); + nn::hac::AesKeygen::generateKey(mNcaHeaderKey.key[1], nca_header_key_source.key[1], nca_header_kek.key); + } + } + + for (size_t j = 0; j < nn::hac::nca::kKeyAreaEncryptionKeyNum; j++) + { + if (key_area_key_source[j] != kNullAesKey && mNcaKeyAreaEncryptionKey[j][i] == kNullAesKey) + { + nn::hac::AesKeygen::generateKey(mNcaKeyAreaEncryptionKey[j][i].key, aes_kek_generation_source.key, key_area_key_source[j].key, aes_key_generation_source.key, master_key[i].key); + } + } + } + + if (ticket_titlekek_source != kNullAesKey && mETicketCommonKey[i] == kNullAesKey) + { + nn::hac::AesKeygen::generateKey(mETicketCommonKey[i].key, ticket_titlekek_source.key, master_key[i].key); + } + if (package2_key_source != kNullAesKey && mPkg2Key[i] == kNullAesKey) + { + nn::hac::AesKeygen::generateKey(mPkg2Key[i].key, package2_key_source.key, master_key[i].key); + } + } + } + + // populate pki root keys + if (pki_root_sign_key != kNullRsa4096Key) + { + sPkiRootKey tmp; + + tmp.name = nn::pki::sign::kRootIssuerStr; + tmp.key_type = nn::pki::sign::SIGN_ALGO_RSA4096; + tmp.rsa4096_key = pki_root_sign_key; + + mPkiRootKeyList.addElement(tmp); + } +} + + +void KeyConfiguration::clearGeneralKeyConfiguration() +{ + mAcidSignKey = kNullRsa2048Key; + mPkg2SignKey = kNullRsa2048Key; + mNcaHeader0SignKey = kNullRsa2048Key; + mXciHeaderSignKey = kNullRsa2048Key; + mPkiRootKeyList.clear(); + + mNcaHeaderKey = kNullAesXtsKey; + mXciHeaderKey = kNullAesKey; + + for (size_t i = 0; i < kMasterKeyNum; i++) + { + mPkg1Key[i] = kNullAesKey; + mPkg2Key[i] = kNullAesKey; + mETicketCommonKey[i] = kNullAesKey; + for (size_t j = 0; j < kNcaKeakNum; j++) + { + mNcaKeyAreaEncryptionKey[j][i] = kNullAesKey; + mNcaKeyAreaEncryptionKey[j][i] = kNullAesKey; + } + } +} + +void KeyConfiguration::clearNcaExternalKeys() +{ + mNcaExternalContentKeyList.clear(); +} + +bool KeyConfiguration::getNcaHeaderKey(fnd::aes::sAesXts128Key& key) const +{ + return copyOutKeyResourceIfExists(mNcaHeaderKey, key, kNullAesXtsKey); +} + +bool KeyConfiguration::getNcaHeader0SignKey(fnd::rsa::sRsa2048Key& key) const +{ + return copyOutKeyResourceIfExists(mNcaHeader0SignKey, key, kNullRsa2048Key); +} + +bool KeyConfiguration::getAcidSignKey(fnd::rsa::sRsa2048Key& key) const +{ + return copyOutKeyResourceIfExists(mAcidSignKey, key, kNullRsa2048Key); +} + +bool KeyConfiguration::getNcaKeyAreaEncryptionKey(byte_t masterkey_index, byte_t keak_type, fnd::aes::sAes128Key& key) const +{ + if (keak_type >= kNcaKeakNum || masterkey_index >= kMasterKeyNum) + { + return false; + } + return copyOutKeyResourceIfExists(mNcaKeyAreaEncryptionKey[keak_type][masterkey_index], key, kNullAesKey); +} + +bool KeyConfiguration::getNcaKeyAreaEncryptionKeyHw(byte_t masterkey_index, byte_t keak_type, fnd::aes::sAes128Key& key) const +{ + if (keak_type >= kNcaKeakNum || masterkey_index >= kMasterKeyNum) + { + return false; + } + return copyOutKeyResourceIfExists(mNcaKeyAreaEncryptionKeyHw[keak_type][masterkey_index], key, kNullAesKey); +} + +void KeyConfiguration::addNcaExternalContentKey(const byte_t rights_id[nn::hac::nca::kRightsIdLen], const fnd::aes::sAes128Key& key) +{ + sNcaExternalContentKey tmp; + memcpy(tmp.rights_id.data, rights_id, nn::hac::nca::kRightsIdLen); + tmp.key = key; + + if (mNcaExternalContentKeyList.hasElement(tmp)) + return; + + mNcaExternalContentKeyList.addElement(tmp); +} + +bool KeyConfiguration::getNcaExternalContentKey(const byte_t rights_id[nn::hac::nca::kRightsIdLen], fnd::aes::sAes128Key& key) const +{ + sRightsId id; + bool res_exists = false; + + memcpy(id.data, rights_id, nn::hac::nca::kRightsIdLen); + for (size_t i = 0; i < mNcaExternalContentKeyList.size(); i++) + { + if (mNcaExternalContentKeyList[i].rights_id == id) + { + res_exists = true; + key = mNcaExternalContentKeyList[i].key; + break; + } + } + + return res_exists; +} + +bool KeyConfiguration::getPkg1Key(byte_t masterkey_index, fnd::aes::sAes128Key& key) const +{ + if (masterkey_index >= kMasterKeyNum) + { + return false; + } + return copyOutKeyResourceIfExists(mPkg1Key[masterkey_index], key, kNullAesKey); +} + +bool KeyConfiguration::getPkg2Key(byte_t masterkey_index, fnd::aes::sAes128Key& key) const +{ + if (masterkey_index >= kMasterKeyNum) + { + return false; + } + return copyOutKeyResourceIfExists(mPkg2Key[masterkey_index], key, kNullAesKey); +} + +bool KeyConfiguration::getPkg2SignKey(fnd::rsa::sRsa2048Key& key) const +{ + return copyOutKeyResourceIfExists(mPkg2SignKey, key, kNullRsa2048Key); +} + +bool KeyConfiguration::getXciHeaderSignKey(fnd::rsa::sRsa2048Key& key) const +{ + return copyOutKeyResourceIfExists(mXciHeaderSignKey, key, kNullRsa2048Key); +} + +bool KeyConfiguration::getXciHeaderKey(fnd::aes::sAes128Key& key) const +{ + return copyOutKeyResourceIfExists(mXciHeaderKey, key, kNullAesKey); +} + +bool KeyConfiguration::getETicketCommonKey(byte_t masterkey_index, fnd::aes::sAes128Key& key) const +{ + if (masterkey_index >= kMasterKeyNum) + { + return false; + } + return copyOutKeyResourceIfExists(mETicketCommonKey[masterkey_index], key, kNullAesKey); +} + +bool KeyConfiguration::getPkiRootSignKey(const std::string& root_name, fnd::rsa::sRsa4096Key& key) const +{ + bool res_exists = false; + for (size_t i = 0; i < mPkiRootKeyList.size(); i++) + { + if (root_name == mPkiRootKeyList[i].name && mPkiRootKeyList[i].key_type == nn::pki::sign::SIGN_ALGO_RSA4096) + { + res_exists = true; + key = mPkiRootKeyList[i].rsa4096_key; + break; + } + } + + return res_exists; +} + +bool KeyConfiguration::getPkiRootSignKey(const std::string& root_name, fnd::rsa::sRsa2048Key& key) const +{ + bool res_exists = false; + for (size_t i = 0; i < mPkiRootKeyList.size(); i++) + { + if (root_name == mPkiRootKeyList[i].name && mPkiRootKeyList[i].key_type == nn::pki::sign::SIGN_ALGO_RSA2048) + { + res_exists = true; + key = mPkiRootKeyList[i].rsa2048_key; + break; + } + } + + return res_exists; +} + +bool KeyConfiguration::getPkiRootSignKey(const std::string& root_name, fnd::ecdsa::sEcdsa240Key& key) const +{ + bool res_exists = false; + for (size_t i = 0; i < mPkiRootKeyList.size(); i++) + { + if (root_name == mPkiRootKeyList[i].name && mPkiRootKeyList[i].key_type == nn::pki::sign::SIGN_ALGO_ECDSA240) + { + res_exists = true; + key = mPkiRootKeyList[i].ecdsa240_key; + break; + } + } + + return res_exists; +} \ No newline at end of file diff --git a/programs/nstool/source/KeyConfiguration.h b/programs/nstool/source/KeyConfiguration.h new file mode 100644 index 0000000..41a59e9 --- /dev/null +++ b/programs/nstool/source/KeyConfiguration.h @@ -0,0 +1,209 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class KeyConfiguration +{ +public: + KeyConfiguration(); + KeyConfiguration(const KeyConfiguration& other); + + void operator=(const KeyConfiguration& other); + + void importHactoolGenericKeyfile(const std::string& path); + //void importHactoolTitleKeyfile(const std::string& path); + + void clearGeneralKeyConfiguration(); + void clearNcaExternalKeys(); + + // nca keys + bool getNcaHeaderKey(fnd::aes::sAesXts128Key& key) const; + bool getNcaHeader0SignKey(fnd::rsa::sRsa2048Key& key) const; + bool getAcidSignKey(fnd::rsa::sRsa2048Key& key) const; + bool getNcaKeyAreaEncryptionKey(byte_t masterkey_index, byte_t keak_type, fnd::aes::sAes128Key& key) const; + bool getNcaKeyAreaEncryptionKeyHw(byte_t masterkey_index, byte_t keak_type, fnd::aes::sAes128Key& key) const; + + // external content keys + void addNcaExternalContentKey(const byte_t rights_id[nn::hac::nca::kRightsIdLen], const fnd::aes::sAes128Key& key); + bool getNcaExternalContentKey(const byte_t rights_id[nn::hac::nca::kRightsIdLen], fnd::aes::sAes128Key& key) const; + + // pkg1/pkg2 + bool getPkg1Key(byte_t masterkey_index, fnd::aes::sAes128Key& key) const; + bool getPkg2Key(byte_t masterkey_index, fnd::aes::sAes128Key& key) const; + bool getPkg2SignKey(fnd::rsa::sRsa2048Key& key) const; + + // xci keys + bool getXciHeaderSignKey(fnd::rsa::sRsa2048Key& key) const; + bool getXciHeaderKey(fnd::aes::sAes128Key& key) const; + + // ticket + bool getETicketCommonKey(byte_t masterkey_index, fnd::aes::sAes128Key& key) const; + + // pki + bool getPkiRootSignKey(const std::string& root_name, fnd::rsa::sRsa4096Key& key) const; + bool getPkiRootSignKey(const std::string& root_name, fnd::rsa::sRsa2048Key& key) const; + bool getPkiRootSignKey(const std::string& root_name, fnd::ecdsa::sEcdsa240Key& key) const; +private: + const std::string kModuleName = "KeyConfiguration"; + const fnd::aes::sAes128Key kNullAesKey = {{0}}; + const fnd::aes::sAesXts128Key kNullAesXtsKey = {{{0}}}; + const fnd::rsa::sRsa4096Key kNullRsa4096Key = {{0}, {0}, {0}}; + const fnd::rsa::sRsa2048Key kNullRsa2048Key = {{0}, {0}, {0}}; + static const size_t kMasterKeyNum = 0x20; + static const size_t kNcaKeakNum = nn::hac::nca::kKeyAreaEncryptionKeyNum; + + // keynames + enum NameVariantIndex + { + NNTOOLS, + LEGACY_HACTOOL, + LEGACY_0 + }; + static const size_t kNameVariantNum = 3; + const std::string kMasterBase[kNameVariantNum] = { "master", "master", "master" }; + const std::string kPkg1Base[kNameVariantNum] = { "package1", "package1", "package1" }; + const std::string kPkg2Base[kNameVariantNum] = { "package2", "package2", "package2" }; + const std::string kXciHeaderBase[kNameVariantNum] = { "xci_header", "xci_header", "xci_header" }; + const std::string kNcaHeaderBase[kNameVariantNum] = { "nca_header", "header", "nca_header" }; + const std::string kAcidBase[kNameVariantNum] = { "acid", "acid", "acid" }; + const std::string kPkiRootBase[kNameVariantNum] = { "pki_root", "pki_root", "pki_root" }; + const std::string kTicketCommonKeyBase[kNameVariantNum] = { "ticket_commonkey", "titlekek", "ticket_commonkey" }; + const std::string kNcaKeyAreaEncKeyBase[kNameVariantNum] = { "nca_body_keak", "key_area_key", "nca_body_keak" }; + const std::string kNcaKeyAreaEncKeyHwBase[kNameVariantNum] = { "nca_body_keakhw", "key_area_hw_key", "nca_body_keakhw" }; + const std::string kKekGenBase[kNameVariantNum] = { "aes_kek_generation", "aes_kek_generation", "aes_kek_generation" }; + const std::string kKeyGenBase[kNameVariantNum] = { "aes_key_generation", "aes_key_generation", "aes_key_generation" }; + + // misc str + const std::string kKeyStr = "key"; + const std::string kKekStr = "kek"; + const std::string kSourceStr = "source"; + const std::string kRsaKeyModulus = "sign_key_modulus"; + const std::string kRsaKeyPrivate = "sign_key_private"; + const std::string kNcaKeyAreaKeyIndexStr[kNcaKeakNum] = { "application", "ocean", "system" }; + const std::string kKeyIndex[kMasterKeyNum] = {"00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f","10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f"}; + + struct sRightsId + { + byte_t data[nn::hac::nca::kRightsIdLen]; + + void operator=(const sRightsId& other) + { + memcpy(this->data, other.data, nn::hac::nca::kRightsIdLen); + } + + bool operator==(const sRightsId& other) const + { + return memcmp(this->data, other.data, nn::hac::nca::kRightsIdLen) == 0; + } + + bool operator!=(const sRightsId& other) const + { + return !(operator==(other)); + } + }; + + struct sNcaExternalContentKey + { + sRightsId rights_id; + fnd::aes::sAes128Key key; + + void operator=(const sNcaExternalContentKey& other) + { + rights_id = other.rights_id; + key = other.key; + } + + bool operator==(const sNcaExternalContentKey& other) const + { + return (rights_id == other.rights_id) \ + && (key == other.key); + } + + bool operator!=(const sNcaExternalContentKey& other) const + { + return !(operator==(other)); + } + }; + + struct sPkiRootKey + { + std::string name; + nn::pki::sign::SignatureAlgo key_type; + fnd::rsa::sRsa4096Key rsa4096_key; + fnd::rsa::sRsa2048Key rsa2048_key; + fnd::ecdsa::sEcdsa240Key ecdsa240_key; + + void operator=(const sPkiRootKey& other) + { + name = other.name; + key_type = other.key_type; + rsa4096_key = other.rsa4096_key; + rsa2048_key = other.rsa2048_key; + ecdsa240_key = other.ecdsa240_key; + } + + bool operator==(const sPkiRootKey& other) const + { + return (name == other.name) \ + && (key_type == other.key_type) \ + && (rsa4096_key == other.rsa4096_key) \ + && (rsa2048_key == other.rsa2048_key) \ + && (ecdsa240_key == other.ecdsa240_key); + } + + bool operator!=(const sPkiRootKey& other) const + { + return !(operator==(other)); + } + }; + + + /* general key config */ + // acid + fnd::rsa::sRsa2048Key mAcidSignKey; + + // pkg1 and pkg2 + fnd::aes::sAes128Key mPkg1Key[kMasterKeyNum]; + fnd::rsa::sRsa2048Key mPkg2SignKey; + fnd::aes::sAes128Key mPkg2Key[kMasterKeyNum]; + + // nca + fnd::rsa::sRsa2048Key mNcaHeader0SignKey; + fnd::aes::sAesXts128Key mNcaHeaderKey; + fnd::aes::sAes128Key mNcaKeyAreaEncryptionKey[kNcaKeakNum][kMasterKeyNum]; + fnd::aes::sAes128Key mNcaKeyAreaEncryptionKeyHw[kNcaKeakNum][kMasterKeyNum]; + + // xci + fnd::rsa::sRsa2048Key mXciHeaderSignKey; + fnd::aes::sAes128Key mXciHeaderKey; + + // ticket + fnd::aes::sAes128Key mETicketCommonKey[kMasterKeyNum]; + + // pki + fnd::List mPkiRootKeyList; + + /* Nca External Keys */ + fnd::List mNcaExternalContentKeyList; + + template + bool copyOutKeyResourceIfExists(const T& src, T& dst, const T& null_sample) const + { + bool resource_exists = false; + + if (src != null_sample) + { + resource_exists = true; + dst = src; + } + + return resource_exists; + } +}; \ No newline at end of file diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index 90de628..781e471 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -15,7 +15,6 @@ NcaProcess::NcaProcess() : mFile(nullptr), mOwnIFile(false), - mKeyset(nullptr), mCliOutputMode(_BIT(OUTPUT_BASIC)), mVerify(false), mListFs(false) @@ -72,9 +71,9 @@ void NcaProcess::setInputFile(fnd::IFile* file, bool ownIFile) mOwnIFile = ownIFile; } -void NcaProcess::setKeyset(const sKeyset* keyset) +void NcaProcess::setKeyCfg(const KeyConfiguration& keycfg) { - mKeyset = keyset; + mKeyCfg = keycfg; } void NcaProcess::setCliOutputMode(CliOutputMode type) @@ -127,7 +126,9 @@ void NcaProcess::importHeader() mFile->read((byte_t*)&mHdrBlock, 0, sizeof(nn::hac::sNcaHeaderBlock)); // decrypt header block - nn::hac::NcaUtils::decryptNcaHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, mKeyset->nca.header_key); + fnd::aes::sAesXts128Key header_key; + mKeyCfg.getNcaHeaderKey(header_key); + nn::hac::NcaUtils::decryptNcaHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, header_key); // generate header hash fnd::sha::Sha256((byte_t*)&mHdrBlock.header, sizeof(nn::hac::sNcaHeader), mHdrHash.bytes); @@ -141,112 +142,93 @@ void NcaProcess::generateNcaBodyEncryptionKeys() // create zeros key fnd::aes::sAes128Key zero_aesctr_key; memset(zero_aesctr_key.key, 0, sizeof(zero_aesctr_key)); - fnd::aes::sAesXts128Key zero_aesxts_key; - memset(zero_aesxts_key.key, 0, sizeof(zero_aesxts_key)); // get key data from header byte_t masterkey_rev = nn::hac::NcaUtils::getMasterKeyRevisionFromKeyGeneration(mHdr.getKeyGeneration()); byte_t keak_index = mHdr.getKaekIndex(); // process key area - sKeys::sKeyAreaKey keak; + sKeys::sKeyAreaKey kak; + fnd::aes::sAes128Key key_area_enc_key; for (size_t i = 0; i < nn::hac::nca::kAesKeyNum; i++) { if (mHdr.getEncAesKeys()[i] != zero_aesctr_key) { - keak.index = (byte_t)i; - keak.enc = mHdr.getEncAesKeys()[i]; - if (i < 4 && mKeyset->nca.key_area_key[keak_index][masterkey_rev] != zero_aesctr_key) + kak.index = (byte_t)i; + kak.enc = mHdr.getEncAesKeys()[i]; + // key[0-3] + if (i < 4 && mKeyCfg.getNcaKeyAreaEncryptionKey(masterkey_rev, keak_index, key_area_enc_key) == true) { - keak.decrypted = true; - nn::hac::AesKeygen::generateKey(keak.dec.key, keak.enc.key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key); + kak.decrypted = true; + nn::hac::AesKeygen::generateKey(kak.dec.key, kak.enc.key, key_area_enc_key.key); + } + // key[4] + else if (i == 4 && mKeyCfg.getNcaKeyAreaEncryptionKeyHw(masterkey_rev, keak_index, key_area_enc_key) == true) + { + kak.decrypted = true; + nn::hac::AesKeygen::generateKey(kak.dec.key, kak.enc.key, key_area_enc_key.key); } else { - keak.decrypted = false; + kak.decrypted = false; } - mBodyKeys.keak_list.addElement(keak); + mContentKey.kak_list.addElement(kak); } } // set flag to indicate that the keys are not available - mBodyKeys.aes_ctr.isSet = false; - mBodyKeys.aes_xts.isSet = false; + mContentKey.aes_ctr.isSet = false; // if this has a rights id, the key needs to be sourced from a ticket if (mHdr.hasRightsId() == true) { - // if the titlekey_kek is available - if (mKeyset->ticket.titlekey_kek[masterkey_rev] != zero_aesctr_key) + fnd::aes::sAes128Key tmp_key; + if (mKeyCfg.getNcaExternalContentKey(mHdr.getRightsId(), tmp_key) == true) { - // the title key is provided (sourced from ticket) - if (mKeyset->nca.manual_title_key_aesctr != zero_aesctr_key) + mContentKey.aes_ctr = tmp_key; + } + else if (mKeyCfg.getNcaExternalContentKey(kDummyRightsIdForUserTitleKey, tmp_key) == true) + { + fnd::aes::sAes128Key common_key; + if (mKeyCfg.getETicketCommonKey(masterkey_rev, common_key) == true) { - nn::hac::AesKeygen::generateKey(mBodyKeys.aes_ctr.var.key, mKeyset->nca.manual_title_key_aesctr.key, mKeyset->ticket.titlekey_kek[masterkey_rev].key); - mBodyKeys.aes_ctr.isSet = true; - } - if (mKeyset->nca.manual_title_key_aesxts != zero_aesxts_key) - { - nn::hac::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[0], mKeyset->nca.manual_title_key_aesxts.key[0], mKeyset->ticket.titlekey_kek[masterkey_rev].key); - nn::hac::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[1], mKeyset->nca.manual_title_key_aesxts.key[1], mKeyset->ticket.titlekey_kek[masterkey_rev].key); - mBodyKeys.aes_xts.isSet = true; + nn::hac::AesKeygen::generateKey(tmp_key.key, tmp_key.key, common_key.key); } + mContentKey.aes_ctr = tmp_key; } } // otherwise decrypt key area else { - fnd::aes::sAes128Key keak_aesctr_key = zero_aesctr_key; - fnd::aes::sAesXts128Key keak_aesxts_key = zero_aesxts_key; - for (size_t i = 0; i < mBodyKeys.keak_list.size(); i++) + fnd::aes::sAes128Key kak_aes_ctr = zero_aesctr_key; + for (size_t i = 0; i < mContentKey.kak_list.size(); i++) { - if (mBodyKeys.keak_list[i].index == nn::hac::nca::KEY_AESCTR && mBodyKeys.keak_list[i].decrypted) + if (mContentKey.kak_list[i].index == nn::hac::nca::KEY_AESCTR && mContentKey.kak_list[i].decrypted) { - keak_aesctr_key = mBodyKeys.keak_list[i].dec; - } - else if (mBodyKeys.keak_list[i].index == nn::hac::nca::KEY_AESXTS_0 && mBodyKeys.keak_list[i].decrypted) - { - memcpy(keak_aesxts_key.key[0], mBodyKeys.keak_list[i].dec.key, sizeof(fnd::aes::sAes128Key)); - } - else if (mBodyKeys.keak_list[i].index == nn::hac::nca::KEY_AESXTS_1 && mBodyKeys.keak_list[i].decrypted) - { - memcpy(keak_aesxts_key.key[1], mBodyKeys.keak_list[i].dec.key, sizeof(fnd::aes::sAes128Key)); + kak_aes_ctr = mContentKey.kak_list[i].dec; } } - if (keak_aesctr_key != zero_aesctr_key) + if (kak_aes_ctr != zero_aesctr_key) { - mBodyKeys.aes_ctr = keak_aesctr_key; - } - if (keak_aesxts_key != zero_aesxts_key) - { - mBodyKeys.aes_xts = keak_aesxts_key; + mContentKey.aes_ctr = kak_aes_ctr; } } // if the keys weren't generated, check if the keys were supplied by the user - if (mBodyKeys.aes_ctr.isSet == false && mKeyset->nca.manual_body_key_aesctr != zero_aesctr_key) + if (mContentKey.aes_ctr.isSet == false) { - mBodyKeys.aes_ctr = mKeyset->nca.manual_body_key_aesctr; - } - if (mBodyKeys.aes_xts.isSet == false && mKeyset->nca.manual_body_key_aesxts != zero_aesxts_key) - { - mBodyKeys.aes_xts = mKeyset->nca.manual_body_key_aesxts; + if (mKeyCfg.getNcaExternalContentKey(kDummyRightsIdForUserBodyKey, mContentKey.aes_ctr.var) == true) + mContentKey.aes_ctr.isSet = true; } + if (_HAS_BIT(mCliOutputMode, OUTPUT_KEY_DATA)) { - if (mBodyKeys.aes_ctr.isSet) + if (mContentKey.aes_ctr.isSet) { - std::cout << "[NCA Body Key]" << std::endl; - std::cout << " AES-CTR Key: " << fnd::SimpleTextOutput::arrayToString(mBodyKeys.aes_ctr.var.key, sizeof(mBodyKeys.aes_ctr.var), true, "") << std::endl; - } - - if (mBodyKeys.aes_xts.isSet) - { - std::cout << "[NCA Body Key]" << std::endl; - std::cout << " AES-XTS Key0: " << fnd::SimpleTextOutput::arrayToString(mBodyKeys.aes_xts.var.key[0], sizeof(mBodyKeys.aes_ctr.var), true, "") << std::endl; - std::cout << " AES-XTS Key1: " << fnd::SimpleTextOutput::arrayToString(mBodyKeys.aes_xts.var.key[1], sizeof(mBodyKeys.aes_ctr.var), true, "") << std::endl; + std::cout << "[NCA Content Key]" << std::endl; + std::cout << " AES-CTR Key: " << fnd::SimpleTextOutput::arrayToString(mContentKey.aes_ctr.var.key, sizeof(mContentKey.aes_ctr.var), true, "") << std::endl; } } @@ -321,9 +303,9 @@ void NcaProcess::generatePartitionConfiguration() } else if (info.enc_type == nn::hac::nca::CRYPT_AESCTR) { - if (mBodyKeys.aes_ctr.isSet == false) + if (mContentKey.aes_ctr.isSet == false) throw fnd::Exception(kModuleName, "AES-CTR Key was not determined"); - info.reader = new OffsetAdjustedIFile(new AesCtrWrappedIFile(mFile, SHARED_IFILE, mBodyKeys.aes_ctr.var, info.aes_ctr), OWN_IFILE, info.offset, info.size); + info.reader = new OffsetAdjustedIFile(new AesCtrWrappedIFile(mFile, SHARED_IFILE, mContentKey.aes_ctr.var, info.aes_ctr), OWN_IFILE, info.offset, info.size); } else if (info.enc_type == nn::hac::nca::CRYPT_AESXTS || info.enc_type == nn::hac::nca::CRYPT_AESCTREX) { @@ -365,7 +347,9 @@ void NcaProcess::generatePartitionConfiguration() void NcaProcess::validateNcaSignatures() { // validate signature[0] - if (fnd::rsa::pss::rsaVerify(mKeyset->nca.header_sign_key, fnd::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_main) != 0) + fnd::rsa::sRsa2048Key sign0_key; + mKeyCfg.getNcaHeader0SignKey(sign0_key); + if (fnd::rsa::pss::rsaVerify(sign0_key, fnd::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_main) != 0) { std::cout << "[WARNING] NCA Header Main Signature: FAIL" << std::endl; } @@ -434,21 +418,21 @@ void NcaProcess::displayHeader() std::cout << " RightsId: " << fnd::SimpleTextOutput::arrayToString(mHdr.getRightsId(), nn::hac::nca::kRightsIdLen, true, "") << std::endl; } - if (mBodyKeys.keak_list.size() > 0 && _HAS_BIT(mCliOutputMode, OUTPUT_KEY_DATA)) + if (mContentKey.kak_list.size() > 0 && _HAS_BIT(mCliOutputMode, OUTPUT_KEY_DATA)) { std::cout << " Key Area:" << std::endl; std::cout << " <--------------------------------------------------------------------------->" << std::endl; std::cout << " | IDX | ENCRYPTED KEY | DECRYPTED KEY |" << std::endl; std::cout << " |-----|----------------------------------|----------------------------------|" << std::endl; - for (size_t i = 0; i < mBodyKeys.keak_list.size(); i++) + for (size_t i = 0; i < mContentKey.kak_list.size(); i++) { - std::cout << " | " << std::dec << std::setw(3) << std::setfill(' ') << (uint32_t)mBodyKeys.keak_list[i].index << " | "; + std::cout << " | " << std::dec << std::setw(3) << std::setfill(' ') << (uint32_t)mContentKey.kak_list[i].index << " | "; - std::cout << fnd::SimpleTextOutput::arrayToString(mBodyKeys.keak_list[i].enc.key, 16, false, "") << " | "; + std::cout << fnd::SimpleTextOutput::arrayToString(mContentKey.kak_list[i].enc.key, 16, false, "") << " | "; - if (mBodyKeys.keak_list[i].decrypted) - std::cout << fnd::SimpleTextOutput::arrayToString(mBodyKeys.keak_list[i].dec.key, 16, false, ""); + if (mContentKey.kak_list[i].decrypted) + std::cout << fnd::SimpleTextOutput::arrayToString(mContentKey.kak_list[i].dec.key, 16, false, ""); else std::cout << " "; diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index a9b0806..b263278 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -4,6 +4,7 @@ #include #include #include "HashTreeMeta.h" +#include "KeyConfiguration.h" #include "nstool.h" @@ -18,7 +19,7 @@ public: // generic void setInputFile(fnd::IFile* file, bool ownIFile); - void setKeyset(const sKeyset* keyset); + void setKeyCfg(const KeyConfiguration& keycfg); void setCliOutputMode(CliOutputMode type); void setVerifyMode(bool verify); @@ -36,7 +37,7 @@ private: // user options fnd::IFile* mFile; bool mOwnIFile; - const sKeyset* mKeyset; + KeyConfiguration mKeyCfg; CliOutputMode mCliOutputMode; bool mVerify; @@ -84,11 +85,10 @@ private: return !(*this == other); } }; - fnd::List keak_list; + fnd::List kak_list; sOptional aes_ctr; - sOptional aes_xts; - } mBodyKeys; + } mContentKey; struct sPartitionInfo { diff --git a/programs/nstool/source/NpdmProcess.cpp b/programs/nstool/source/NpdmProcess.cpp index b9f5fe6..71b9f14 100644 --- a/programs/nstool/source/NpdmProcess.cpp +++ b/programs/nstool/source/NpdmProcess.cpp @@ -5,7 +5,6 @@ NpdmProcess::NpdmProcess() : mFile(nullptr), mOwnIFile(false), - mKeyset(nullptr), mCliOutputMode(_BIT(OUTPUT_BASIC)), mVerify(false) { @@ -57,9 +56,9 @@ void NpdmProcess::setInputFile(fnd::IFile* file, bool ownIFile) mOwnIFile = ownIFile; } -void NpdmProcess::setKeyset(const sKeyset* keyset) +void NpdmProcess::setKeyCfg(const KeyConfiguration& keycfg) { - mKeyset = keyset; + mKeyCfg = keycfg; } void NpdmProcess::setCliOutputMode(CliOutputMode type) @@ -95,7 +94,11 @@ void NpdmProcess::importNpdm() void NpdmProcess::validateAcidSignature(const nn::hac::AccessControlInfoDescBinary& acid) { try { - acid.validateSignature(mKeyset->acid_sign_key); + fnd::rsa::sRsa2048Key acid_sign_key; + if (mKeyCfg.getAcidSignKey(acid_sign_key) != true) + throw fnd::Exception(); + + acid.validateSignature(acid_sign_key); } catch (...) { std::cout << "[WARNING] ACID Signature: FAIL" << std::endl; diff --git a/programs/nstool/source/NpdmProcess.h b/programs/nstool/source/NpdmProcess.h index d476493..68c3523 100644 --- a/programs/nstool/source/NpdmProcess.h +++ b/programs/nstool/source/NpdmProcess.h @@ -3,6 +3,7 @@ #include #include #include +#include "KeyConfiguration.h" #include "nstool.h" @@ -15,7 +16,7 @@ public: void process(); void setInputFile(fnd::IFile* file, bool ownIFile); - void setKeyset(const sKeyset* keyset); + void setKeyCfg(const KeyConfiguration& keycfg); void setCliOutputMode(CliOutputMode type); void setVerifyMode(bool verify); @@ -26,7 +27,7 @@ private: fnd::IFile* mFile; bool mOwnIFile; - const sKeyset* mKeyset; + KeyConfiguration mKeyCfg; CliOutputMode mCliOutputMode; bool mVerify; diff --git a/programs/nstool/source/PkiCertProcess.cpp b/programs/nstool/source/PkiCertProcess.cpp index bc71e09..f9912a3 100644 --- a/programs/nstool/source/PkiCertProcess.cpp +++ b/programs/nstool/source/PkiCertProcess.cpp @@ -39,9 +39,9 @@ void PkiCertProcess::setInputFile(fnd::IFile* file, bool ownIFile) mOwnIFile = ownIFile; } -void PkiCertProcess::setKeyset(const sKeyset* keyset) +void PkiCertProcess::setKeyCfg(const KeyConfiguration& keycfg) { - mKeyset = keyset; + mKeyCfg = keycfg; } void PkiCertProcess::setCliOutputMode(CliOutputMode mode) @@ -80,7 +80,7 @@ void PkiCertProcess::validateCerts() try { - pki.setRootKey(mKeyset->pki.root_sign_key); + pki.setKeyCfg(mKeyCfg); pki.addCertificates(mCert); } catch (const fnd::Exception& e) diff --git a/programs/nstool/source/PkiCertProcess.h b/programs/nstool/source/PkiCertProcess.h index e7dd144..964a70d 100644 --- a/programs/nstool/source/PkiCertProcess.h +++ b/programs/nstool/source/PkiCertProcess.h @@ -6,6 +6,7 @@ #include #include #include +#include "KeyConfiguration.h" #include "nstool.h" class PkiCertProcess @@ -17,7 +18,7 @@ public: void process(); void setInputFile(fnd::IFile* file, bool ownIFile); - void setKeyset(const sKeyset* keyset); + void setKeyCfg(const KeyConfiguration& keycfg); void setCliOutputMode(CliOutputMode type); void setVerifyMode(bool verify); @@ -27,7 +28,7 @@ private: fnd::IFile* mFile; bool mOwnIFile; - const sKeyset* mKeyset; + KeyConfiguration mKeyCfg; CliOutputMode mCliOutputMode; bool mVerify; diff --git a/programs/nstool/source/PkiValidator.cpp b/programs/nstool/source/PkiValidator.cpp index 70c1589..37bb9b5 100644 --- a/programs/nstool/source/PkiValidator.cpp +++ b/programs/nstool/source/PkiValidator.cpp @@ -9,7 +9,7 @@ PkiValidator::PkiValidator() clearCertificates(); } -void PkiValidator::setRootKey(const fnd::rsa::sRsa4096Key& root_key) +void PkiValidator::setKeyCfg(const KeyConfiguration& keycfg) { // save a copy of the certificate bank fnd::List> old_certs = mCertificateBank; @@ -18,7 +18,7 @@ void PkiValidator::setRootKey(const fnd::rsa::sRsa4096Key& root_key) mCertificateBank.clear(); // overwrite the root key - mRootKey = root_key; + mKeyCfg = keycfg; // if there were certificates before, reimport them (so they are checked against the new root key) if (old_certs.size() > 0) @@ -96,13 +96,28 @@ void PkiValidator::validateSignature(const std::string& issuer, nn::pki::sign::S int sig_validate_res = -1; // special case if signed by Root - if (issuer == nn::pki::sign::kRootIssuerStr) + if (issuer.find('-', 0) == std::string::npos) { - if (sign_algo != nn::pki::sign::SIGN_ALGO_RSA4096) + fnd::rsa::sRsa4096Key rsa4096_pub; + fnd::rsa::sRsa2048Key rsa2048_pub; + fnd::ecdsa::sEcdsa240Key ecdsa_pub; + + if (mKeyCfg.getPkiRootSignKey(issuer, rsa4096_pub) == true && sign_algo == nn::pki::sign::SIGN_ALGO_RSA4096) { - throw fnd::Exception(kModuleName, "Issued by Root, but does not have a RSA4096 signature"); + sig_validate_res = fnd::rsa::pkcs::rsaVerify(rsa4096_pub, getCryptoHashAlgoFromEsSignHashAlgo(hash_algo), hash.data(), signature.data()); + } + else if (mKeyCfg.getPkiRootSignKey(issuer, rsa2048_pub) == true && sign_algo == nn::pki::sign::SIGN_ALGO_RSA2048) + { + sig_validate_res = fnd::rsa::pkcs::rsaVerify(rsa2048_pub, getCryptoHashAlgoFromEsSignHashAlgo(hash_algo), hash.data(), signature.data()); + } + else if (mKeyCfg.getPkiRootSignKey(issuer, ecdsa_pub) == true && sign_algo == nn::pki::sign::SIGN_ALGO_ECDSA240) + { + throw fnd::Exception(kModuleName, "ECDSA signatures are not supported"); + } + else + { + throw fnd::Exception(kModuleName, "Public key for issuer \"" + issuer + "\" does not exist."); } - sig_validate_res = fnd::rsa::pkcs::rsaVerify(mRootKey, getCryptoHashAlgoFromEsSignHashAlgo(hash_algo), hash.data(), signature.data()); } else { diff --git a/programs/nstool/source/PkiValidator.h b/programs/nstool/source/PkiValidator.h index 6ef0bd2..7b7a0ab 100644 --- a/programs/nstool/source/PkiValidator.h +++ b/programs/nstool/source/PkiValidator.h @@ -6,13 +6,14 @@ #include #include #include +#include "KeyConfiguration.h" class PkiValidator { public: PkiValidator(); - void setRootKey(const fnd::rsa::sRsa4096Key& root_key); + void setKeyCfg(const KeyConfiguration& keycfg); void addCertificates(const fnd::List>& certs); void addCertificate(const nn::pki::SignedData& cert); void clearCertificates(); @@ -22,8 +23,7 @@ public: private: const std::string kModuleName = "NNPkiValidator"; - - fnd::rsa::sRsa4096Key mRootKey; + KeyConfiguration mKeyCfg; fnd::List> mCertificateBank; void makeCertIdent(const nn::pki::SignedData& cert, std::string& ident) const; diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index b556caa..ce1e929 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -1,6 +1,7 @@ #include "UserSettings.h" #include "version.h" #include "PkiValidator.h" +#include "KeyConfiguration.h" #include #include #include @@ -95,9 +96,9 @@ const std::string UserSettings::getInputPath() const return mInputPath; } -const sKeyset& UserSettings::getKeyset() const +const KeyConfiguration& UserSettings::getKeyCfg() const { - return mKeyset; + return mKeyCfg; } FileType UserSettings::getFileType() const @@ -385,36 +386,23 @@ void UserSettings::populateCmdArgs(const std::vector& arg_list, sCm void UserSettings::populateKeyset(sCmdArgs& args) { - fnd::aes::sAes128Key zeros_aes_key; - fnd::aes::sAesXts128Key zeros_aes_xts_key; - memset((void*)&zeros_aes_key, 0, sizeof(fnd::aes::sAes128Key)); - memset((void*)&zeros_aes_xts_key, 0, sizeof(fnd::aes::sAesXts128Key)); - memset((void*)&mKeyset, 0, sizeof(sKeyset)); - - fnd::ResourceFileReader res; if (args.keyset_path.isSet) { - res.processFile(*args.keyset_path); + mKeyCfg.importHactoolGenericKeyfile(*args.keyset_path); } else { // open other resource files in $HOME/.switch/prod.keys (or $HOME/.switch/dev.keys if -d/--dev is set). - std::string home; - if (home.empty()) fnd::io::getEnvironVar(home, "HOME"); - if (home.empty()) fnd::io::getEnvironVar(home, "USERPROFILE"); - if (home.empty()) return; - - const std::string kKeysetNameStr[2] = {"prod.keys", "dev.keys"}; - const std::string kHomeSwitchDirStr = ".switch"; - std::string keyset_path; - fnd::io::appendToPath(keyset_path, home); - fnd::io::appendToPath(keyset_path, kHomeSwitchDirStr); - fnd::io::appendToPath(keyset_path, kKeysetNameStr[args.devkit_keys.isSet ? *args.devkit_keys : 0]); + getSwitchPath(keyset_path); + if (keyset_path.empty()) + return; + + fnd::io::appendToPath(keyset_path, kGeneralKeyfileName[args.devkit_keys.isSet]); try { - res.processFile(keyset_path); + mKeyCfg.importHactoolGenericKeyfile(keyset_path); } catch (const fnd::Exception&) { @@ -422,132 +410,29 @@ void UserSettings::populateKeyset(sCmdArgs& args) } } + - // suffix - const std::string kKeyStr = "key"; - const std::string kKekStr = "kek"; - const std::string kSourceStr = "source"; - const std::string kRsaKeySuffix[2] = {"sign_key_private", "sign_key_modulus"}; - const std::string kKeyIndex[kMasterKeyNum] = {"00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f","10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f"}; - // keyname bases - const std::string kMasterBase = "master"; - const std::string kPackage1Base = "package1"; - const std::string kPackage2Base = "package2"; - const std::string kXciHeaderBase = "xci_header"; - const std::string kNcaHeaderBase[2] = {"header", "nca_header"}; - const std::string kKekGenSource = "aes_kek_generation"; - const std::string kKeyGenSource = "aes_key_generation"; - const std::string kAcidBase = "acid"; - const std::string kPkiRootBase = "pki_root"; - const std::string kTicketCommonKeyBase[2] = { "titlekek", "ticket_commonkey" }; - const std::string kNcaBodyBase[2] = {"key_area_key", "nca_body_keak"}; - const std::string kNcaBodyKeakIndexName[3] = {"application", "ocean", "system"}; - - - // sources - fnd::aes::sAes128Key master_key[kMasterKeyNum] = { zeros_aes_key }; - fnd::aes::sAes128Key package2_key_source = zeros_aes_key; - fnd::aes::sAes128Key ticket_titlekek_source = zeros_aes_key; - fnd::aes::sAes128Key key_area_key_source[3] = { zeros_aes_key, zeros_aes_key, zeros_aes_key }; - fnd::aes::sAes128Key aes_kek_generation_source = zeros_aes_key; - fnd::aes::sAes128Key aes_key_generation_source = zeros_aes_key; - fnd::aes::sAes128Key nca_header_kek_source = zeros_aes_key; - fnd::aes::sAesXts128Key nca_header_key_source = zeros_aes_xts_key; - - -#define _CONCAT_2_STRINGS(str1, str2) ((str1) + "_" + (str2)) -#define _CONCAT_3_STRINGS(str1, str2, str3) ((str1) + "_"+ (str2) + "_" + (str3)) - - std::string key,val; - -#define _SAVE_KEYDATA(key_name, array, len) \ - key = (key_name); \ - val = res[key]; \ - if (val.empty() == false) { \ - decodeHexStringToBytes(key, val, (byte_t*)array, len); \ - } - - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kPackage2Base, kKeyStr, kSourceStr), package2_key_source.key, 0x10); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[0], kSourceStr), ticket_titlekek_source.key, 0x10); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[1], kSourceStr), ticket_titlekek_source.key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[0], kSourceStr), key_area_key_source[0].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[1], kSourceStr), key_area_key_source[1].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[2], kSourceStr), key_area_key_source[2].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[1], kNcaBodyKeakIndexName[0], kSourceStr), key_area_key_source[0].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[1], kNcaBodyKeakIndexName[1], kSourceStr), key_area_key_source[1].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[1], kNcaBodyKeakIndexName[2], kSourceStr), key_area_key_source[2].key, 0x10); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kKekGenSource, kSourceStr), aes_kek_generation_source.key, 0x10); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kKeyGenSource, kSourceStr), aes_key_generation_source.key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaHeaderBase[0], kKekStr, kSourceStr), nca_header_kek_source.key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaHeaderBase[0], kKeyStr, kSourceStr), nca_header_key_source.key, 0x20); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaHeaderBase[1], kKekStr, kSourceStr), nca_header_kek_source.key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaHeaderBase[1], kKeyStr, kSourceStr), nca_header_key_source.key, 0x20); - - // Store Key Variants/Derivatives - for (size_t i = 0; i < kMasterKeyNum; i++) - { - - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kMasterBase, kKeyStr, kKeyIndex[i]), master_key[i].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kPackage1Base, kKeyStr, kKeyIndex[i]), mKeyset.package1_key[i].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kPackage2Base, kKeyStr, kKeyIndex[i]), mKeyset.package2_key[i].key, 0x10); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[0], kKeyIndex[i]), mKeyset.ticket.titlekey_kek[i].key, 0x10); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[1], kKeyIndex[i]), mKeyset.ticket.titlekey_kek[i].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[0], kKeyIndex[i]), mKeyset.nca.key_area_key[0][i].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[1], kKeyIndex[i]), mKeyset.nca.key_area_key[1][i].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[2], kKeyIndex[i]), mKeyset.nca.key_area_key[2][i].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[1], kNcaBodyKeakIndexName[0], kKeyIndex[i]), mKeyset.nca.key_area_key[0][i].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[1], kNcaBodyKeakIndexName[1], kKeyIndex[i]), mKeyset.nca.key_area_key[1][i].key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[1], kNcaBodyKeakIndexName[2], kKeyIndex[i]), mKeyset.nca.key_area_key[2][i].key, 0x10); - } - - // store nca header key - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[0], kKeyStr), mKeyset.nca.header_key.key[0], 0x20); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[1], kKeyStr), mKeyset.nca.header_key.key[0], 0x20); - - // store xci header key - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase, kKeyStr), mKeyset.xci.header_key.key, 0x10); - - // store rsa keys - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[1], kRsaKeySuffix[0]), mKeyset.nca.header_sign_key.priv_exponent, fnd::rsa::kRsa2048Size); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[1], kRsaKeySuffix[1]), mKeyset.nca.header_sign_key.modulus, fnd::rsa::kRsa2048Size); - - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase, kRsaKeySuffix[0]), mKeyset.xci.header_sign_key.priv_exponent, fnd::rsa::kRsa2048Size); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase, kRsaKeySuffix[1]), mKeyset.xci.header_sign_key.modulus, fnd::rsa::kRsa2048Size); - - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kAcidBase, kRsaKeySuffix[0]), mKeyset.acid_sign_key.priv_exponent, fnd::rsa::kRsa2048Size); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kAcidBase, kRsaKeySuffix[1]), mKeyset.acid_sign_key.modulus, fnd::rsa::kRsa2048Size); - - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPackage2Base, kRsaKeySuffix[0]), mKeyset.package2_sign_key.priv_exponent, fnd::rsa::kRsa2048Size); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPackage2Base, kRsaKeySuffix[1]), mKeyset.package2_sign_key.modulus, fnd::rsa::kRsa2048Size); - - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPkiRootBase, kRsaKeySuffix[0]), mKeyset.pki.root_sign_key.priv_exponent, fnd::rsa::kRsa4096Size); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPkiRootBase, kRsaKeySuffix[1]), mKeyset.pki.root_sign_key.modulus, fnd::rsa::kRsa4096Size); - - - // save keydata from input args if (args.nca_bodykey.isSet) { - if (args.nca_bodykey.var.length() == (sizeof(fnd::aes::sAes128Key)*2)) - { - decodeHexStringToBytes("--bodykey", args.nca_bodykey.var, mKeyset.nca.manual_body_key_aesctr.key, sizeof(fnd::aes::sAes128Key)); - } - else - { - decodeHexStringToBytes("--bodykey", args.nca_bodykey.var, mKeyset.nca.manual_body_key_aesxts.key[0], sizeof(fnd::aes::sAesXts128Key)); - } + fnd::aes::sAes128Key tmp_key; + fnd::Vec tmp_raw; + fnd::SimpleTextOutput::stringToArray(args.nca_bodykey.var, tmp_raw); + if (tmp_raw.size() != sizeof(fnd::aes::sAes128Key)) + throw fnd::Exception(kModuleName, "Key: \"--bodykey\" has incorrect length"); + memcpy(tmp_key.key, tmp_raw.data(), 16); + mKeyCfg.addNcaExternalContentKey(kDummyRightsIdForUserBodyKey, tmp_key); } if (args.nca_titlekey.isSet) { - if (args.nca_titlekey.var.length() == (sizeof(fnd::aes::sAes128Key)*2)) - { - decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesctr.key, sizeof(fnd::aes::sAes128Key)); - } - else - { - decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesxts.key[0], sizeof(fnd::aes::sAesXts128Key)); - } + fnd::aes::sAes128Key tmp_key; + fnd::Vec tmp_raw; + fnd::SimpleTextOutput::stringToArray(args.nca_bodykey.var, tmp_raw); + if (tmp_raw.size() != sizeof(fnd::aes::sAes128Key)) + throw fnd::Exception(kModuleName, "Key: \"--titlekey\" has incorrect length"); + memcpy(tmp_key.key, tmp_raw.data(), 16); + mKeyCfg.addNcaExternalContentKey(kDummyRightsIdForUserTitleKey, tmp_key); } // import certificate chain @@ -601,7 +486,7 @@ void UserSettings::populateKeyset(sCmdArgs& args) try { - pki_validator.setRootKey(mKeyset.pki.root_sign_key); + pki_validator.setKeyCfg(mKeyCfg); pki_validator.addCertificates(mCertChain); pki_validator.validateSignature(tik.getBody().getIssuer(), tik.getSignature().getSignType(), tik.getSignature().getSignature(), tik_hash); } @@ -615,75 +500,24 @@ void UserSettings::populateKeyset(sCmdArgs& args) // extract title key if (tik.getBody().getTitleKeyEncType() == nn::es::ticket::AES128_CBC) { - memcpy(mKeyset.nca.manual_title_key_aesctr.key, tik.getBody().getEncTitleKey(), fnd::aes::kAes128KeySize); + fnd::aes::sAes128Key enc_title_key; + memcpy(enc_title_key.key, tik.getBody().getEncTitleKey(), 16); + fnd::aes::sAes128Key common_key, external_content_key; + if (mKeyCfg.getETicketCommonKey(nn::hac::NcaUtils::getMasterKeyRevisionFromKeyGeneration(tik.getBody().getCommonKeyId()), common_key) == true) + { + nn::hac::AesKeygen::generateKey(external_content_key.key, tik.getBody().getEncTitleKey(), common_key.key); + mKeyCfg.addNcaExternalContentKey(tik.getBody().getRightsId(), external_content_key); + } + else + { + std::cout << "[WARNING] Titlekey not imported from ticket because commonkey was not available" << std::endl; + } } else { std::cout << "[WARNING] Titlekey not imported from ticket because it is personalised" << std::endl; } } - -#undef _SAVE_KEYDATA -#undef _CONCAT_3_STRINGS -#undef _CONCAT_2_STRINGS - - // Derive keys - for (size_t i = 0; i < kMasterKeyNum; i++) - { - if (master_key[i] != zeros_aes_key) - { - if (aes_kek_generation_source != zeros_aes_key && aes_key_generation_source != zeros_aes_key) - { - if (i == 0 && nca_header_kek_source != zeros_aes_key && nca_header_key_source != zeros_aes_xts_key) - { - if (mKeyset.nca.header_key == zeros_aes_xts_key) - { - fnd::aes::sAes128Key nca_header_kek; - nn::hac::AesKeygen::generateKey(nca_header_kek.key, aes_kek_generation_source.key, nca_header_kek_source.key, aes_key_generation_source.key, master_key[i].key); - nn::hac::AesKeygen::generateKey(mKeyset.nca.header_key.key[0], nca_header_key_source.key[0], nca_header_kek.key); - nn::hac::AesKeygen::generateKey(mKeyset.nca.header_key.key[1], nca_header_key_source.key[1], nca_header_kek.key); - //printf("nca header key[0] "); - //fnd::SimpleTextOutput::hexDump(mKeyset.nca.header_key.key[0], 0x10); - //printf("nca header key[1] "); - //fnd::SimpleTextOutput::hexDump(mKeyset.nca.header_key.key[1], 0x10); - } - } - - for (size_t j = 0; j < nn::hac::nca::kKeyAreaEncryptionKeyNum; j++) - { - if (key_area_key_source[j] != zeros_aes_key && mKeyset.nca.key_area_key[j][i] == zeros_aes_key) - { - nn::hac::AesKeygen::generateKey(mKeyset.nca.key_area_key[j][i].key, aes_kek_generation_source.key, key_area_key_source[j].key, aes_key_generation_source.key, master_key[i].key); - //printf("nca keak %d/%02d ", j, i); - //fnd::SimpleTextOutput::hexDump(mKeyset.nca.key_area_key[j][i].key, 0x10); - } - } - } - - if (ticket_titlekek_source != zeros_aes_key && mKeyset.ticket.titlekey_kek[i] == zeros_aes_key) - { - nn::hac::AesKeygen::generateKey(mKeyset.ticket.titlekey_kek[i].key, ticket_titlekek_source.key, master_key[i].key); - //printf("ticket titlekek %02d ", i); - //fnd::SimpleTextOutput::hexDump(mKeyset.ticket.titlekey_kek[i].key, 0x10); - } - if (package2_key_source != zeros_aes_key && mKeyset.package2_key[i] == zeros_aes_key) - { - nn::hac::AesKeygen::generateKey(mKeyset.package2_key[i].key, package2_key_source.key, master_key[i].key); - //printf("package2 key %02d ", i); - //fnd::SimpleTextOutput::hexDump(mKeyset.package2_key[i].key, 0x10); - } - } - /* - for (size_t j = 0; j < nn::hac::nca::kKeyAreaEncryptionKeyNum; j++) - { - if (mKeyset.nca.key_area_key[j][i] != zeros_aes_key) - { - printf("nca body keak %d/%02d ", j, i); - fnd::SimpleTextOutput::hexDump(mKeyset.nca.key_area_key[j][i].key, 0x10); - } - } - */ - } } void UserSettings::populateUserSettings(sCmdArgs& args) @@ -747,21 +581,6 @@ void UserSettings::populateUserSettings(sCmdArgs& args) throw fnd::Exception(kModuleName, "Unknown file type."); } - -void UserSettings::decodeHexStringToBytes(const std::string& name, const std::string& str, byte_t* out, size_t out_len) -{ - size_t size = str.size(); - if ((size % 2) || ((size / 2) != out_len)) - { - throw fnd::Exception(kModuleName, "Key: \"" + name + "\" has incorrect length"); - } - - for (size_t i = 0; i < out_len; i++) - { - out[i] = (charToByte(str[i * 2]) << 4) | charToByte(str[(i * 2) + 1]); - } -} - FileType UserSettings::getFileTypeFromString(const std::string& type_str) { std::string str = type_str; @@ -880,7 +699,9 @@ bool UserSettings::determineValidNcaFromSample(const fnd::Vec& sample) c if (sample.size() < nn::hac::nca::kHeaderSize) return false; - nn::hac::NcaUtils::decryptNcaHeader(sample.data(), nca_raw, mKeyset.nca.header_key); + fnd::aes::sAesXts128Key header_key; + mKeyCfg.getNcaHeaderKey(header_key); + nn::hac::NcaUtils::decryptNcaHeader(sample.data(), nca_raw, header_key); if (nca_header->st_magic.get() != nn::hac::nca::kNca2StructMagic && nca_header->st_magic.get() != nn::hac::nca::kNca3StructMagic) return false; @@ -1019,3 +840,25 @@ nn::hac::npdm::InstructionType UserSettings::getInstructionTypeFromString(const return type; } + +void UserSettings::getHomePath(std::string& path) const +{ + // open other resource files in $HOME/.switch/prod.keys (or $HOME/.switch/dev.keys if -d/--dev is set). + path.clear(); + if (path.empty()) fnd::io::getEnvironVar(path, "HOME"); + if (path.empty()) fnd::io::getEnvironVar(path, "USERPROFILE"); + if (path.empty()) return; +} + +void UserSettings::getSwitchPath(std::string& path) const +{ + std::string home; + home.clear(); + getHomePath(home); + if (home.empty()) + return; + + path.clear(); + fnd::io::appendToPath(path, home); + fnd::io::appendToPath(path, kHomeSwitchDirStr); +} \ No newline at end of file diff --git a/programs/nstool/source/UserSettings.h b/programs/nstool/source/UserSettings.h index a3fd6ed..84450ba 100644 --- a/programs/nstool/source/UserSettings.h +++ b/programs/nstool/source/UserSettings.h @@ -8,6 +8,7 @@ #include #include #include "nstool.h" +#include "KeyConfiguration.h" class UserSettings { @@ -19,7 +20,7 @@ public: // generic options const std::string getInputPath() const; - const sKeyset& getKeyset() const; + const KeyConfiguration& getKeyCfg() const; FileType getFileType() const; bool isVerifyFile() const; CliOutputMode getCliOutputMode() const; @@ -44,8 +45,15 @@ public: const sOptional& getAssetNacpPath() const; const fnd::List>& getCertificateChain() const; + void dumpKeys() const; + private: const std::string kModuleName = "UserSettings"; + + const std::string kHomeSwitchDirStr = ".switch"; + const std::string kGeneralKeyfileName[2] = { "prod.keys", "dev.keys" }; + const std::string kTitleKeyfileName = "title.keys"; + struct sCmdArgs { @@ -81,7 +89,7 @@ private: std::string mInputPath; FileType mFileType; - sKeyset mKeyset; + KeyConfiguration mKeyCfg; bool mVerifyFile; CliOutputMode mOutputMode; @@ -109,7 +117,6 @@ private: void populateCmdArgs(const std::vector& arg_list, sCmdArgs& cmd_args); void populateKeyset(sCmdArgs& args); void populateUserSettings(sCmdArgs& args); - void decodeHexStringToBytes(const std::string& name, const std::string& str, byte_t* out, size_t out_len); FileType getFileTypeFromString(const std::string& type_str); FileType determineFileTypeFromFile(const std::string& path); bool determineValidNcaFromSample(const fnd::Vec& sample) const; @@ -118,4 +125,6 @@ private: bool determineValidEsCertFromSample(const fnd::Vec& sample) const; bool determineValidEsTikFromSample(const fnd::Vec& sample) const; nn::hac::npdm::InstructionType getInstructionTypeFromString(const std::string& type_str); + void getHomePath(std::string& path) const; + void getSwitchPath(std::string& path) const; }; \ No newline at end of file diff --git a/programs/nstool/source/XciProcess.cpp b/programs/nstool/source/XciProcess.cpp index b535204..48bde39 100644 --- a/programs/nstool/source/XciProcess.cpp +++ b/programs/nstool/source/XciProcess.cpp @@ -8,7 +8,6 @@ XciProcess::XciProcess() : mFile(nullptr), mOwnIFile(false), - mKeyset(nullptr), mCliOutputMode(_BIT(OUTPUT_BASIC)), mVerify(false), mListFs(false), @@ -50,9 +49,9 @@ void XciProcess::setInputFile(fnd::IFile* file, bool ownIFile) mOwnIFile = ownIFile; } -void XciProcess::setKeyset(const sKeyset* keyset) +void XciProcess::setKeyCfg(const KeyConfiguration& keycfg) { - mKeyset = keyset; + mKeyCfg = keycfg; } void XciProcess::setCliOutputMode(CliOutputMode type) @@ -89,7 +88,10 @@ void XciProcess::importHeader() // allocate memory for and decrypt sXciHeader scratch.alloc(sizeof(nn::hac::sXciHeader)); - nn::hac::XciUtils::decryptXciHeader((const byte_t*)&mHdrPage.header, scratch.data(), mKeyset->xci.header_key.key); + + fnd::aes::sAes128Key header_key; + mKeyCfg.getXciHeaderKey(header_key); + nn::hac::XciUtils::decryptXciHeader((const byte_t*)&mHdrPage.header, scratch.data(), header_key.key); // deserialise header mHdr.fromBytes(scratch.data(), scratch.size()); @@ -198,9 +200,11 @@ bool XciProcess::validateRegionOfFile(size_t offset, size_t len, const byte_t* t void XciProcess::validateXciSignature() { + fnd::rsa::sRsa2048Key header_sign_key; fnd::sha::sSha256Hash calc_hash; fnd::sha::Sha256((byte_t*)&mHdrPage.header, sizeof(nn::hac::sXciHeader), calc_hash.bytes); - if (fnd::rsa::pkcs::rsaVerify(mKeyset->xci.header_sign_key, fnd::sha::HASH_SHA256, calc_hash.bytes, mHdrPage.signature) != 0) + mKeyCfg.getXciHeaderSignKey(header_sign_key); + if (fnd::rsa::pkcs::rsaVerify(header_sign_key, fnd::sha::HASH_SHA256, calc_hash.bytes, mHdrPage.signature) != 0) { std::cout << "[WARNING] XCI Header Signature: FAIL" << std::endl; } diff --git a/programs/nstool/source/XciProcess.h b/programs/nstool/source/XciProcess.h index c3da185..0978b18 100644 --- a/programs/nstool/source/XciProcess.h +++ b/programs/nstool/source/XciProcess.h @@ -4,11 +4,10 @@ #include #include #include - -#include "nstool.h" - +#include "KeyConfiguration.h" #include "PfsProcess.h" +#include "nstool.h" class XciProcess { @@ -20,7 +19,7 @@ public: // generic void setInputFile(fnd::IFile* file, bool ownIFile); - void setKeyset(const sKeyset* keyset); + void setKeyCfg(const KeyConfiguration& keycfg); void setCliOutputMode(CliOutputMode type); void setVerifyMode(bool verify); @@ -34,7 +33,7 @@ private: fnd::IFile* mFile; bool mOwnIFile; - const sKeyset* mKeyset; + KeyConfiguration mKeyCfg; CliOutputMode mCliOutputMode; bool mVerify; diff --git a/programs/nstool/source/main.cpp b/programs/nstool/source/main.cpp index 6a30e29..9659241 100644 --- a/programs/nstool/source/main.cpp +++ b/programs/nstool/source/main.cpp @@ -41,7 +41,7 @@ int main(int argc, char** argv) xci.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE); - xci.setKeyset(&user_set.getKeyset()); + xci.setKeyCfg(user_set.getKeyCfg()); xci.setCliOutputMode(user_set.getCliOutputMode()); xci.setVerifyMode(user_set.isVerifyFile()); @@ -90,7 +90,7 @@ int main(int argc, char** argv) NcaProcess nca; nca.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE); - nca.setKeyset(&user_set.getKeyset()); + nca.setKeyCfg(user_set.getKeyCfg()); nca.setCliOutputMode(user_set.getCliOutputMode()); nca.setVerifyMode(user_set.isVerifyFile()); @@ -112,7 +112,7 @@ int main(int argc, char** argv) NpdmProcess npdm; npdm.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE); - npdm.setKeyset(&user_set.getKeyset()); + npdm.setKeyCfg(user_set.getKeyCfg()); npdm.setCliOutputMode(user_set.getCliOutputMode()); npdm.setVerifyMode(user_set.isVerifyFile()); @@ -180,7 +180,7 @@ int main(int argc, char** argv) PkiCertProcess cert; cert.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE); - cert.setKeyset(&user_set.getKeyset()); + cert.setKeyCfg(user_set.getKeyCfg()); cert.setCliOutputMode(user_set.getCliOutputMode()); cert.setVerifyMode(user_set.isVerifyFile()); @@ -191,7 +191,7 @@ int main(int argc, char** argv) EsTikProcess tik; tik.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE); - tik.setKeyset(&user_set.getKeyset()); + tik.setKeyCfg(user_set.getKeyCfg()); tik.setCertificateChain(user_set.getCertificateChain()); tik.setCliOutputMode(user_set.getCliOutputMode()); tik.setVerifyMode(user_set.isVerifyFile()); diff --git a/programs/nstool/source/nstool.h b/programs/nstool/source/nstool.h index ba36f7a..6bdf571 100644 --- a/programs/nstool/source/nstool.h +++ b/programs/nstool/source/nstool.h @@ -1,5 +1,4 @@ #pragma once -#pragma once #include #include #include @@ -61,50 +60,5 @@ struct sOptional inline T& operator*() { return var; } }; -struct sKeyset -{ - fnd::rsa::sRsa2048Key acid_sign_key; - fnd::aes::sAes128Key package1_key[kMasterKeyNum]; - fnd::rsa::sRsa2048Key package2_sign_key; - fnd::aes::sAes128Key package2_key[kMasterKeyNum]; - - struct sNcaData - { - fnd::rsa::sRsa2048Key header_sign_key; - fnd::aes::sAesXts128Key header_key; - fnd::aes::sAes128Key key_area_key[kNcaKeakNum][kMasterKeyNum]; - - fnd::aes::sAes128Key manual_title_key_aesctr; - fnd::aes::sAesXts128Key manual_title_key_aesxts; - fnd::aes::sAes128Key manual_body_key_aesctr; - fnd::aes::sAesXts128Key manual_body_key_aesxts; - } nca; - - struct sXciData - { - fnd::rsa::sRsa2048Key header_sign_key; - fnd::aes::sAes128Key header_key; - } xci; - - struct sTicketData - { - fnd::rsa::sRsa2048Key sign_key; - fnd::aes::sAes128Key titlekey_kek[kMasterKeyNum]; - } ticket; - - struct sPkiData - { - fnd::rsa::sRsa4096Key root_sign_key; - } pki; -}; - -inline byte_t charToByte(char chr) -{ - if (chr >= 'a' && chr <= 'f') - return (chr - 'a') + 0xa; - else if (chr >= 'A' && chr <= 'F') - return (chr - 'A') + 0xa; - else if (chr >= '0' && chr <= '9') - return chr - '0'; - return 0; -} +const byte_t kDummyRightsIdForUserTitleKey[nn::hac::nca::kRightsIdLen] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; +const byte_t kDummyRightsIdForUserBodyKey[nn::hac::nca::kRightsIdLen] = {0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe}; \ No newline at end of file