mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 18:26:39 +00:00
QoL changes
* codebase: remove all references to Lockpick / Lockpick_RCM. * keys: retrieve Atmosphère's key generation in keysLoadKeyset(). * keys: replace all "key_gen_val" references with "mkey_index". * keys: move latest master key checks in keysReadKeysFromFile() to keysDeriveMasterKeys(). * keys: update latest master key checks to determine if nxdumptool's hardcoded master key vectors are outdated, using the key generation value from Atmosphère. If the master key vectors are outdated, and the newer master key(s) are not available, an error will be displayed. * nxdt_utils: update logic in utilsReplaceIllegalCharacters() to replace consecutive illegal characters with a single underscore. * nxdt_utils: move servicesGetExosphereApiVersion() to nxdt_utils as utilsGetExosphereApiVersion(). * nxdt_utils: define internal UtilsExosphereApiVersion struct, which is used to parse the output from utilsGetExosphereApiVersion(). * nxdt_utils: add utilsGetAtmosphereVersion(), utilsGetAtmosphereKeyGeneration() and utilsGetAtmosphereTargetFirmware() functions. * services: remove servicesGetExosphereApiVersion(). * services: Atmosphère version is now retrieved via utilsGetAtmosphereVersion().
This commit is contained in:
parent
e3cd2a15eb
commit
54c5677cd0
9 changed files with 203 additions and 125 deletions
|
@ -30,7 +30,7 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Loads (and derives) keydata from sysmodule program memory, the Lockpick_RCM keys file and hardcoded/obfuscated information.
|
||||
/// Loads (and derives) keydata from sysmodule program memory, a keys file and hardcoded/obfuscated information.
|
||||
/// Must be called (and succeed) before calling any of the functions below.
|
||||
bool keysLoadKeyset(void);
|
||||
|
||||
|
|
|
@ -85,6 +85,17 @@ FsFileSystem *utilsGetSdCardFileSystemObject(void);
|
|||
/// Must be used after closing a file handle from the SD card.
|
||||
bool utilsCommitSdCardFileSystemChanges(void);
|
||||
|
||||
/// Returns an integer that represents the full Atmosphère release version.
|
||||
/// Use the HOSVER_* macros to retrieve specific version numbers from it.
|
||||
u32 utilsGetAtmosphereVersion(void);
|
||||
|
||||
/// Returns an integer that represents the global key generation used by Atmosphère.
|
||||
/// The returned value represents an index, so it doesn't match 1:1 the NcaKeyGeneration enum.
|
||||
u8 utilsGetAtmosphereKeyGeneration(void);
|
||||
|
||||
/// Fills the provided SdkAddOnVersion element with the target firmware set by Atmosphère.
|
||||
void utilsGetAtmosphereTargetFirmware(SdkAddOnVersion *out);
|
||||
|
||||
/// Returns a UtilsCustomFirmwareType value.
|
||||
u8 utilsGetCustomFirmwareType(void);
|
||||
|
||||
|
@ -113,9 +124,10 @@ void utilsJoinThread(Thread *thread);
|
|||
/// If the buffer isn't big enough to hold both its current contents and the new formatted string, it will be resized.
|
||||
__attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...);
|
||||
|
||||
/// Replaces illegal FAT characters in the provided UTF-8 string with underscores.
|
||||
/// If 'ascii_only' is set to true, all codepoints outside the [0x20,0x7F) range will also be replaced with underscores.
|
||||
/// Replacements are performed on a per-codepoint basis, which means the string length can be reduced by this function.
|
||||
/// Replaces illegal filesystem characters in the provided NULL-terminated UTF-8 string with underscores ('_').
|
||||
/// If 'ascii_only' is set to true, all codepoints outside of the [0x20,0x7F) range will also be replaced with underscores.
|
||||
/// Replacements are performed on a per-codepoint basis, which means the string size in bytes can be reduced by this function.
|
||||
/// Furthermore, if multiple, consecutive illegal characters are found, they will all get replaced by a single underscore.
|
||||
void utilsReplaceIllegalCharacters(char *str, bool ascii_only);
|
||||
|
||||
/// Trims whitespace characters from the provided string.
|
||||
|
|
|
@ -40,11 +40,11 @@ bool servicesInitialize();
|
|||
/// Closes services previously initialized by servicesInitialize().
|
||||
void servicesClose();
|
||||
|
||||
/// Check if a service has been initialized using its name.
|
||||
/// Check if a service has been initialized by this interface using its name.
|
||||
bool servicesCheckInitializedServiceByName(const char *name);
|
||||
|
||||
/// Checks if a service is running using its name.
|
||||
/// Wrapper for the "AtmosphereHasService" SM API extension from Atmosphère and Atmosphère-based CFWs.
|
||||
/// Checks if a service is running using its name, even if it wasn't initialized by this interface.
|
||||
/// This servers as a wrapper for the "AtmosphereHasService" SM API extension from Atmosphère and Atmosphère-based CFWs.
|
||||
/// Perfectly safe to use under development units. Not available in older Atmosphère releases.
|
||||
bool servicesCheckRunningServiceByName(const char *name);
|
||||
|
||||
|
|
|
@ -95,8 +95,8 @@
|
|||
#define NRO_PATH DEVOPTAB_SDMC_DEVICE APP_BASE_PATH NRO_NAME
|
||||
#define NRO_TMP_PATH NRO_PATH ".tmp"
|
||||
|
||||
#define PROD_KEYS_FILE_PATH DEVOPTAB_SDMC_DEVICE HBMENU_BASE_PATH "prod.keys" /* Location used by Lockpick_RCM for retail unit keys. */
|
||||
#define DEV_KEYS_FILE_PATH DEVOPTAB_SDMC_DEVICE HBMENU_BASE_PATH "dev.keys" /* Location used by Lockpick_RCM for development unit keys. */
|
||||
#define PROD_KEYS_FILE_PATH DEVOPTAB_SDMC_DEVICE HBMENU_BASE_PATH "prod.keys" /* Retail unit keys. */
|
||||
#define DEV_KEYS_FILE_PATH DEVOPTAB_SDMC_DEVICE HBMENU_BASE_PATH "dev.keys" /* Development unit keys. */
|
||||
|
||||
#define LOG_FILE_NAME APP_TITLE ".log"
|
||||
#define LOG_BUF_SIZE 0x400000 /* 4 MiB. */
|
||||
|
@ -133,4 +133,7 @@
|
|||
|
||||
#define DISCORD_SERVER_URL "https://discord.gg/SCbbcQx"
|
||||
|
||||
// TODO: remove this after the PoC builds are no longer needed.
|
||||
#define PRERELEASE_URL GITHUB_URL "/" APP_AUTHOR "/nxdumptool/releases/tag/rewrite-prerelease"
|
||||
|
||||
#endif /* __DEFINES_H__ */
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"description": "Nintendo Switch Dump Tool",
|
||||
|
||||
"copyright": "Licensed under GPLv3+\n\u00A9 2020 - 2022 {0}\n{1}",
|
||||
"copyright": "Licensed under GPLv3+\n\u00A9 2020 - 2024 {0}\n{1}",
|
||||
|
||||
"dependencies": {
|
||||
"header": "Dependencies",
|
||||
|
@ -16,15 +16,16 @@
|
|||
"header": "Acknowledgments",
|
||||
"line_00": "\uE016 Switchbrew and libnx contributors.",
|
||||
"line_01": "\uE016 SciresM, for hactool, hac2l and Atmosphère-NX.",
|
||||
"line_02": "\uE016 shchmue, for Lockpick and its runtime key-collection algorithm, as well as helping with reverse engineering tasks.",
|
||||
"line_03": "\uE016 Adubbz, for Tinfoil and its ES service bindings.",
|
||||
"line_04": "\uE016 RattletraPM, for the awesome icon.",
|
||||
"line_05": "\uE016 Whovian9369, for being a key piece throughout the whole development by providing lots of testing and cool ideas.",
|
||||
"line_06": "\uE016 liamadvance, Shadów and SimonTime, for their tremendous help in understanding Nintendo Switch file and data formats.",
|
||||
"line_07": "\uE016 The folks from NSWDB.COM and No-Intro.org, for being kind enough to put up public APIs to perform online checksum lookups.",
|
||||
"line_08": "\uE016 The folks at the nxdumptool Discord server.",
|
||||
"line_09": "\uE016 The Comfy Boyes, for always being awesome and supportive. You know who you are.",
|
||||
"line_10": "\uE016 And, at last but not least, you! Thank you for using my work!"
|
||||
"line_02": "\uE016 Moosehunter, for LibHac and hactoolnet.",
|
||||
"line_03": "\uE016 shchmue, for the runtime key derivation algorithm, as well as helping with reverse engineering tasks.",
|
||||
"line_04": "\uE016 Adubbz, for Tinfoil and its ES service bindings.",
|
||||
"line_05": "\uE016 RattletraPM, for the awesome icon.",
|
||||
"line_06": "\uE016 Whovian9369, for being a key piece throughout the whole development by providing lots of testing and cool ideas.",
|
||||
"line_07": "\uE016 liamadvance, Shadów and SimonTime, for their tremendous help in understanding Nintendo Switch file and data formats.",
|
||||
"line_08": "\uE016 The folks from NSWDB.COM and No-Intro.org, for being kind enough to put up public APIs to perform online checksum lookups.",
|
||||
"line_09": "\uE016 The folks at the nxdumptool Discord server.",
|
||||
"line_10": "\uE016 The Comfy Boyes, for always being awesome and supportive. You know who you are.",
|
||||
"line_11": "\uE016 And, at last but not least, you! Thank you for using my work!"
|
||||
},
|
||||
|
||||
"links": {
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace nxdt::views
|
|||
/* Acknowledgments. */
|
||||
this->addView(new brls::Header("about_tab/acknowledgments/header"_i18n));
|
||||
for(int i = 0; i < 7; i++) this->addView(new AboutTabLabel(brls::LabelStyle::SMALL, i18n::getStr(fmt::format("about_tab/acknowledgments/line_{:02d}", i))));
|
||||
for(int i = 7; i < 11; i++) this->addView(new brls::Label(brls::LabelStyle::SMALL, i18n::getStr(fmt::format("about_tab/acknowledgments/line_{:02d}", i)), true));
|
||||
for(int i = 7; i < 12; i++) this->addView(new brls::Label(brls::LabelStyle::SMALL, i18n::getStr(fmt::format("about_tab/acknowledgments/line_{:02d}", i)), true));
|
||||
|
||||
/* Additional links and resources. */
|
||||
this->addView(new brls::Header("about_tab/links/header"_i18n));
|
||||
|
|
|
@ -35,15 +35,15 @@
|
|||
|
||||
typedef struct {
|
||||
///< AES-128-ECB key used to derive master KEKs from Erista master KEK sources.
|
||||
///< Only available in Erista units. Retrieved from the Lockpick_RCM keys file.
|
||||
///< Only available in Erista units. Retrieved from the keys file.
|
||||
u8 tsec_root_key[AES_128_KEY_SIZE];
|
||||
|
||||
///< AES-128-ECB key used to derive master KEKs from Mariko master KEK sources.
|
||||
///< Only available in Mariko units. Retrieved from the Lockpick_RCM keys file -- if available, because it must be manually bruteforced on a PC after running Lockpick_RCM.
|
||||
///< Only available in Mariko units. Retrieved from the keys file -- if available, because it must be manually bruteforced on a PC after dumping keys using a BPMP payload.
|
||||
u8 mariko_kek[AES_128_KEY_SIZE];
|
||||
|
||||
///< AES-128-ECB keys used to decrypt the vast majority of Switch content.
|
||||
///< Derived at runtime using hardcoded key sources and additional keydata retrieved from the Lockpick_RCM keys file.
|
||||
///< Derived at runtime using hardcoded key sources and additional keydata retrieved from the keys file.
|
||||
u8 master_keys[NcaKeyGeneration_Max][AES_128_KEY_SIZE];
|
||||
|
||||
///< AES-128-XTS key needed to handle NCA header crypto.
|
||||
|
@ -55,7 +55,7 @@ typedef struct {
|
|||
u8 nca_kaek[NcaKeyAreaEncryptionKeyIndex_Count][NcaKeyGeneration_Max][AES_128_KEY_SIZE];
|
||||
|
||||
///< AES-128-CTR key needed to decrypt the console-specific eTicket RSA device key stored in PRODINFO.
|
||||
///< Retrieved from the Lockpick_RCM keys file. Verified by decrypting the eTicket RSA device key.
|
||||
///< Retrieved from the keys file. Verified by decrypting the eTicket RSA device key.
|
||||
///< The key itself may or may not be console-specific (personalized), based on the eTicket RSA device key generation value.
|
||||
u8 eticket_rsa_kek[AES_128_KEY_SIZE];
|
||||
|
||||
|
@ -110,10 +110,12 @@ static bool keysGenerateAesKeyFromAesKek(const u8 *kek_src, u8 key_generation, S
|
|||
static bool g_keysetLoaded = false;
|
||||
static Mutex g_keysetMutex = 0;
|
||||
|
||||
static u8 g_atmosphereKeyGeneration = 0;
|
||||
|
||||
static SetCalRsa2048DeviceKey g_eTicketRsaDeviceKey = {0};
|
||||
static KeysNxKeyset g_nxKeyset = {0};
|
||||
|
||||
static bool g_latestMasterKeyAvailable = false;
|
||||
static bool g_tsecRootKeyAvailable = false, g_marikoKekAvailable = false;
|
||||
|
||||
static bool g_wipedSetCal = false;
|
||||
|
||||
|
@ -126,6 +128,10 @@ bool keysLoadKeyset(void)
|
|||
ret = g_keysetLoaded;
|
||||
if (ret) break;
|
||||
|
||||
/* Get Atmosphère's key generation. */
|
||||
/* This actually represents an index, so we must be careful whenever we use it. */
|
||||
g_atmosphereKeyGeneration = utilsGetAtmosphereKeyGeneration();
|
||||
|
||||
/* Get eTicket RSA device key. */
|
||||
Result rc = setcalGetEticketDeviceKey(&g_eTicketRsaDeviceKey);
|
||||
if (R_FAILED(rc))
|
||||
|
@ -134,15 +140,11 @@ bool keysLoadKeyset(void)
|
|||
break;
|
||||
}
|
||||
|
||||
/* Read data from the Lockpick_RCM keys file. */
|
||||
/* Read data from the keys file. */
|
||||
if (!keysReadKeysFromFile()) break;
|
||||
|
||||
/* Derive master keys. */
|
||||
if (!keysDeriveMasterKeys())
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to derive master keys!");
|
||||
break;
|
||||
}
|
||||
if (!keysDeriveMasterKeys()) break;
|
||||
|
||||
/* Derive NCA header key. */
|
||||
if (!keysDeriveNcaHeaderKey()) break;
|
||||
|
@ -187,7 +189,7 @@ const u8 *keysGetNcaHeaderKey(void)
|
|||
const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation)
|
||||
{
|
||||
const u8 *ret = NULL;
|
||||
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
|
||||
const u8 mkey_index = (key_generation ? (key_generation - 1) : key_generation);
|
||||
|
||||
if (kaek_index >= NcaKeyAreaEncryptionKeyIndex_Count)
|
||||
{
|
||||
|
@ -205,11 +207,11 @@ const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation)
|
|||
{
|
||||
if (!g_keysetLoaded) break;
|
||||
|
||||
ret = (const u8*)(g_nxKeyset.nca_kaek[kaek_index][key_gen_val]);
|
||||
ret = (const u8*)(g_nxKeyset.nca_kaek[kaek_index][mkey_index]);
|
||||
|
||||
if (keysIsKeyEmpty(ret))
|
||||
{
|
||||
LOG_MSG_ERROR("NCA KAEK for type %u and generation %u unavailable.", kaek_index, key_gen_val);
|
||||
LOG_MSG_ERROR("NCA KAEK for type %02X and generation %02X unavailable.", kaek_index, mkey_index);
|
||||
ret = NULL;
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +259,7 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o
|
|||
const u8 *keysGetTicketCommonKey(u8 key_generation)
|
||||
{
|
||||
const u8 *ret = NULL;
|
||||
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
|
||||
const u8 mkey_index = (key_generation ? (key_generation - 1) : key_generation);
|
||||
|
||||
if (key_generation > NcaKeyGeneration_Max)
|
||||
{
|
||||
|
@ -269,11 +271,11 @@ const u8 *keysGetTicketCommonKey(u8 key_generation)
|
|||
{
|
||||
if (!g_keysetLoaded) break;
|
||||
|
||||
ret = (const u8*)(g_nxKeyset.ticket_common_keys[key_gen_val]);
|
||||
ret = (const u8*)(g_nxKeyset.ticket_common_keys[mkey_index]);
|
||||
|
||||
if (keysIsKeyEmpty(ret))
|
||||
{
|
||||
LOG_MSG_ERROR("Ticket common key for generation %u unavailable.", key_gen_val);
|
||||
LOG_MSG_ERROR("Ticket common key for generation %02X unavailable.", mkey_index);
|
||||
ret = NULL;
|
||||
}
|
||||
}
|
||||
|
@ -543,10 +545,10 @@ static bool keysReadKeysFromFile(void)
|
|||
char test_name[0x40] = {0};
|
||||
|
||||
const char *keys_file_path = (utilsIsDevelopmentUnit() ? DEV_KEYS_FILE_PATH : PROD_KEYS_FILE_PATH);
|
||||
const bool is_mariko = utilsIsMarikoUnit();
|
||||
|
||||
bool is_mariko = utilsIsMarikoUnit();
|
||||
bool tsec_root_key_available = false, mariko_kek_available = false;
|
||||
bool use_personalized_eticket_rsa_kek = (g_eTicketRsaDeviceKey.generation > 0), eticket_rsa_kek_available = false;
|
||||
bool eticket_rsa_kek_available = false;
|
||||
const char *eticket_rsa_kek_name = (g_eTicketRsaDeviceKey.generation > 0 ? "eticket_rsa_kek_personalized" : "eticket_rsa_kek");
|
||||
|
||||
keys_file = fopen(keys_file_path, "rb");
|
||||
if (!keys_file)
|
||||
|
@ -579,16 +581,16 @@ static bool keysReadKeysFromFile(void)
|
|||
{
|
||||
/* Parse Mariko KEK. */
|
||||
/* This will only appear on Mariko units. */
|
||||
if (!mariko_kek_available)
|
||||
if (!g_marikoKekAvailable)
|
||||
{
|
||||
PARSE_HEX_KEY("mariko_kek", g_nxKeyset.mariko_kek, mariko_kek_available = true; continue);
|
||||
PARSE_HEX_KEY("mariko_kek", g_nxKeyset.mariko_kek, g_marikoKekAvailable = true; continue);
|
||||
}
|
||||
} else {
|
||||
/* Parse TSEC root key. */
|
||||
/* This will only appear on Erista units. */
|
||||
if (!tsec_root_key_available)
|
||||
if (!g_tsecRootKeyAvailable)
|
||||
{
|
||||
PARSE_HEX_KEY_WITH_INDEX("tsec_root_key", TSEC_ROOT_KEY_VERSION, g_nxKeyset.tsec_root_key, tsec_root_key_available = true; continue);
|
||||
PARSE_HEX_KEY_WITH_INDEX("tsec_root_key", TSEC_ROOT_KEY_VERSION, g_nxKeyset.tsec_root_key, g_tsecRootKeyAvailable = true; continue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -596,14 +598,13 @@ static bool keysReadKeysFromFile(void)
|
|||
/* The personalized entry only appears on consoles that use the new PRODINFO key generation scheme. */
|
||||
if (!eticket_rsa_kek_available)
|
||||
{
|
||||
PARSE_HEX_KEY(use_personalized_eticket_rsa_kek ? "eticket_rsa_kek_personalized" : "eticket_rsa_kek", g_nxKeyset.eticket_rsa_kek, eticket_rsa_kek_available = true; continue);
|
||||
PARSE_HEX_KEY(eticket_rsa_kek_name, g_nxKeyset.eticket_rsa_kek, eticket_rsa_kek_available = true; continue);
|
||||
}
|
||||
|
||||
/* Parse master keys, starting with the last known one. */
|
||||
for(u8 i = NcaKeyGeneration_Current; i <= NcaKeyGeneration_Max; i++)
|
||||
for(u8 i = (NcaKeyGeneration_Current - 1); i < NcaKeyGeneration_Max; i++)
|
||||
{
|
||||
u8 key_gen_val = (i - 1);
|
||||
PARSE_HEX_KEY_WITH_INDEX("master_key", key_gen_val, g_nxKeyset.master_keys[key_gen_val], break);
|
||||
PARSE_HEX_KEY_WITH_INDEX("master_key", i, g_nxKeyset.master_keys[i], break);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -624,32 +625,10 @@ static bool keysReadKeysFromFile(void)
|
|||
return false;
|
||||
}
|
||||
|
||||
/* Check if the latest master key was retrieved. */
|
||||
g_latestMasterKeyAvailable = !keysIsKeyEmpty(g_nxKeyset.master_keys[NcaKeyGeneration_Current - 1]);
|
||||
if (!g_latestMasterKeyAvailable)
|
||||
{
|
||||
LOG_MSG_WARNING("Latest known master key (%02X) unavailable in \"%s\". Latest master key derivation will be carried out.", NcaKeyGeneration_Current - 1, keys_file_path);
|
||||
|
||||
/* Make sure we have what we need to derive the latest master key. */
|
||||
if (is_mariko)
|
||||
{
|
||||
if (!mariko_kek_available)
|
||||
{
|
||||
LOG_MSG_ERROR("Mariko KEK unavailable in \"%s\"!", keys_file_path);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!tsec_root_key_available)
|
||||
{
|
||||
LOG_MSG_ERROR("TSEC root key unavailable in \"%s\"!", keys_file_path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Bail out if we couldn't retrieve the eTicket RSA KEK. */
|
||||
if (!eticket_rsa_kek_available)
|
||||
{
|
||||
LOG_MSG_ERROR("eTicket RSA KEK unavailable in \"%s\"!", keys_file_path);
|
||||
LOG_MSG_ERROR("\"%s\" unavailable in \"%s\"!", eticket_rsa_kek_name, keys_file_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -659,34 +638,87 @@ static bool keysReadKeysFromFile(void)
|
|||
static bool keysDeriveMasterKeys(void)
|
||||
{
|
||||
u8 tmp[AES_128_KEY_SIZE] = {0};
|
||||
const u8 latest_mkey_index = (NcaKeyGeneration_Current - 1);
|
||||
bool is_dev = utilsIsDevelopmentUnit();
|
||||
const u8 current_mkey_index = (NcaKeyGeneration_Current - 1); // Convert into an index.
|
||||
const bool is_dev = utilsIsDevelopmentUnit(), is_mariko = utilsIsMarikoUnit(), outdated_mkey_vectors = (g_atmosphereKeyGeneration > current_mkey_index);
|
||||
bool latest_mkey_available = false;
|
||||
|
||||
/* Check if the latest master key was retrieved. */
|
||||
if (outdated_mkey_vectors)
|
||||
{
|
||||
/* Our master key vectors are outdated. */
|
||||
/* This means the user is running both a HOS version with a newer key generation and an Atmosphère release with support for said HOS version. */
|
||||
/* Not everything is lost, though. We just need to check if we parsed all master keys between the last one we know and the one Atmosphère supports (inclusive range). */
|
||||
/* However, since we have no master key vectors for the additional master key(s), we can't reliably test them. */
|
||||
latest_mkey_available = true;
|
||||
|
||||
for(u8 i = current_mkey_index; i <= g_atmosphereKeyGeneration; i++)
|
||||
{
|
||||
if (keysIsKeyEmpty(g_nxKeyset.master_keys[i]))
|
||||
{
|
||||
latest_mkey_available = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Our master key vectors are up-to-date. */
|
||||
/* Just checking if we have the latest known master key should suffice. */
|
||||
latest_mkey_available = !keysIsKeyEmpty(g_nxKeyset.master_keys[current_mkey_index]);
|
||||
}
|
||||
|
||||
/* Only derive the latest master key if it hasn't been populated already. */
|
||||
if (!g_latestMasterKeyAvailable)
|
||||
if (!latest_mkey_available)
|
||||
{
|
||||
if (utilsIsMarikoUnit())
|
||||
/* Bail out immediately if our master key vectors are outdated. */
|
||||
if (outdated_mkey_vectors)
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to derive master keys.\r\n" \
|
||||
"PKG1 key generation (%02X) is higher than the last known key generation (%02X).\r\n" \
|
||||
"Furthermore, one or more of the newer master keys are not available in the keys\r\n" \
|
||||
"file. If you haven't already, please get an updated build at:\r\n%s\r\n%s", \
|
||||
g_atmosphereKeyGeneration, current_mkey_index, PRERELEASE_URL, DISCORD_SERVER_URL);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_MSG_WARNING("Latest known master key (%02X) unavailable. Latest master key derivation will be carried out.", current_mkey_index);
|
||||
|
||||
/* Derive the latest master KEK. */
|
||||
if (is_mariko)
|
||||
{
|
||||
if (!g_marikoKekAvailable)
|
||||
{
|
||||
LOG_MSG_ERROR("\"mariko_kek\" unavailable! Unable to derive master keys.");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Derive the latest master KEK using the hardcoded Mariko master KEK source and the Mariko KEK. */
|
||||
aes128EcbCrypt(tmp, is_dev ? g_marikoMasterKekSourceDev : g_marikoMasterKekSourceProd, g_nxKeyset.mariko_kek, false);
|
||||
} else {
|
||||
if (!g_tsecRootKeyAvailable)
|
||||
{
|
||||
LOG_MSG_ERROR("\"tsec_root_key_%02X\" unavailable! Unable to derive master keys.", TSEC_ROOT_KEY_VERSION);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Derive the latest master KEK using the hardcoded Erista master KEK source and the TSEC root key. */
|
||||
aes128EcbCrypt(tmp, g_eristaMasterKekSource, g_nxKeyset.tsec_root_key, false);
|
||||
}
|
||||
|
||||
/* Derive the latest master key using the hardcoded master key source and the latest master KEK. */
|
||||
aes128EcbCrypt(g_nxKeyset.master_keys[latest_mkey_index], g_masterKeySource, tmp, false);
|
||||
aes128EcbCrypt(g_nxKeyset.master_keys[current_mkey_index], g_masterKeySource, tmp, false);
|
||||
}
|
||||
|
||||
/* Derive all lower master keys using the latest master key and the master key vectors. */
|
||||
for(u8 i = latest_mkey_index; i > NcaKeyGeneration_Since100NUP; i--) aes128EcbCrypt(g_nxKeyset.master_keys[i - 1], is_dev ? g_masterKeyVectorsDev[i] : g_masterKeyVectorsProd[i], \
|
||||
for(u8 i = current_mkey_index; i > NcaKeyGeneration_Since100NUP; i--) aes128EcbCrypt(g_nxKeyset.master_keys[i - 1], is_dev ? g_masterKeyVectorsDev[i] : g_masterKeyVectorsProd[i], \
|
||||
g_nxKeyset.master_keys[i], false);
|
||||
|
||||
/* Check if we derived the right keys. */
|
||||
aes128EcbCrypt(tmp, is_dev ? g_masterKeyVectorsDev[NcaKeyGeneration_Since100NUP] : g_masterKeyVectorsProd[NcaKeyGeneration_Since100NUP], \
|
||||
g_nxKeyset.master_keys[NcaKeyGeneration_Since100NUP], false);
|
||||
|
||||
return keysIsKeyEmpty(tmp);
|
||||
bool ret = keysIsKeyEmpty(tmp);
|
||||
if (!ret) LOG_MSG_ERROR("Master key derivation failed! Wrong keys?");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool keysDeriveNcaHeaderKey(void)
|
||||
|
@ -729,21 +761,21 @@ static bool keysDerivePerGenerationKeys(void)
|
|||
|
||||
for(u8 i = 1; i <= NcaKeyGeneration_Max; i++)
|
||||
{
|
||||
u8 key_gen_val = (i - 1);
|
||||
const u8 mkey_index = (i - 1);
|
||||
|
||||
/* Make sure we're not dealing with an unpopulated master key entry. */
|
||||
if (i > NcaKeyGeneration_Current && keysIsKeyEmpty(g_nxKeyset.master_keys[key_gen_val]))
|
||||
if (i > NcaKeyGeneration_Current && keysIsKeyEmpty(g_nxKeyset.master_keys[mkey_index]))
|
||||
{
|
||||
//LOG_MSG_DEBUG("Master key %02X unavailable.", key_gen_val);
|
||||
//LOG_MSG_DEBUG("\"master_key_%02X\" unavailable.", mkey_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Derive NCA key area keys for this generation. */
|
||||
for(u8 j = 0; j < NcaKeyAreaEncryptionKeyIndex_Count; j++)
|
||||
{
|
||||
if (!keysLoadAesKeyFromAesKek(g_ncaKeyAreaEncryptionKeySources[j], i, option, g_aesKeyGenerationSource, g_nxKeyset.nca_kaek[j][key_gen_val]))
|
||||
if (!keysLoadAesKeyFromAesKek(g_ncaKeyAreaEncryptionKeySources[j], i, option, g_aesKeyGenerationSource, g_nxKeyset.nca_kaek[j][mkey_index]))
|
||||
{
|
||||
LOG_MSG_DEBUG("Failed to derive NCA KAEK for type %u and generation %u!", j, key_gen_val);
|
||||
LOG_MSG_DEBUG("Failed to derive NCA KAEK for type %02X and generation %02X!", j, mkey_index);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
@ -752,7 +784,7 @@ static bool keysDerivePerGenerationKeys(void)
|
|||
if (!success) break;
|
||||
|
||||
/* Derive ticket common key for this generation. */
|
||||
aes128EcbCrypt(g_nxKeyset.ticket_common_keys[key_gen_val], g_ticketCommonKeySource, g_nxKeyset.master_keys[key_gen_val], false);
|
||||
aes128EcbCrypt(g_nxKeyset.ticket_common_keys[mkey_index], g_ticketCommonKeySource, g_nxKeyset.master_keys[mkey_index], false);
|
||||
}
|
||||
|
||||
return success;
|
||||
|
@ -845,9 +877,8 @@ static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void
|
|||
/* Based on splCryptoGenerateAesKek(). Excludes key sealing and device-unique shenanigans. */
|
||||
static bool keysGenerateAesKek(const u8 *kek_src, u8 key_generation, SmcGenerateAesKekOption option, u8 *out_kek)
|
||||
{
|
||||
bool is_device_unique = (option.fields.is_device_unique == 1);
|
||||
u8 key_type_idx = option.fields.key_type_idx;
|
||||
u8 seal_key_idx = option.fields.seal_key_idx;
|
||||
const bool is_device_unique = (option.fields.is_device_unique == 1);
|
||||
const u8 key_type_idx = option.fields.key_type_idx, seal_key_idx = option.fields.seal_key_idx, mkey_index = (key_generation ? (key_generation - 1) : key_generation);
|
||||
|
||||
if (!kek_src || key_generation > NcaKeyGeneration_Max || is_device_unique || key_type_idx >= SmcKeyType_Count || seal_key_idx >= SmcSealKey_Count || \
|
||||
option.fields.reserved != 0 || !out_kek)
|
||||
|
@ -856,15 +887,13 @@ static bool keysGenerateAesKek(const u8 *kek_src, u8 key_generation, SmcGenerate
|
|||
return false;
|
||||
}
|
||||
|
||||
if (key_generation) key_generation--;
|
||||
|
||||
u8 kekek_src[AES_128_KEY_SIZE] = {0}, kekek[AES_128_KEY_SIZE] = {0};
|
||||
const u8 *mkey = g_nxKeyset.master_keys[key_generation];
|
||||
const u8 *mkey = g_nxKeyset.master_keys[mkey_index];
|
||||
|
||||
/* Make sure this master key is available. */
|
||||
if (keysIsKeyEmpty(mkey))
|
||||
{
|
||||
LOG_MSG_ERROR("Master key %02X unavailable!", key_generation);
|
||||
LOG_MSG_ERROR("\"master_key_%02X\" unavailable!", mkey_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,17 @@
|
|||
|
||||
/* Type definitions. */
|
||||
|
||||
/* Reference: https://github.com/Atmosphere-NX/Atmosphere/blob/master/exosphere/program/source/smc/secmon_smc_info.hpp. */
|
||||
typedef struct {
|
||||
SdkAddOnVersion target_firmware;
|
||||
u8 key_generation;
|
||||
u8 ams_ver_micro;
|
||||
u8 ams_ver_minor;
|
||||
u8 ams_ver_major;
|
||||
} UtilsExosphereApiVersion;
|
||||
|
||||
NXDT_ASSERT(UtilsExosphereApiVersion, 0x8);
|
||||
|
||||
typedef struct {
|
||||
u32 major;
|
||||
u32 minor;
|
||||
|
@ -97,10 +108,16 @@ static const size_t g_outputDirsCount = MAX_ELEMENTS(g_outputDirs);
|
|||
|
||||
static bool g_appUpdated = false;
|
||||
|
||||
static const SplConfigItem SplConfigItem_ExosphereApiVersion = (SplConfigItem)65000;
|
||||
|
||||
static UtilsExosphereApiVersion g_exosphereApiVersion = {0};
|
||||
|
||||
/* Function prototypes. */
|
||||
|
||||
static void _utilsGetLaunchPath(void);
|
||||
|
||||
static bool utilsGetExosphereApiVersion(void);
|
||||
|
||||
static void _utilsGetCustomFirmwareType(void);
|
||||
|
||||
static bool _utilsGetProductModel(void);
|
||||
|
@ -163,11 +180,26 @@ bool utilsInitializeResources(void)
|
|||
LOG_MSG_INFO("Horizon OS version: %u.%u.%u.", HOSVER_MAJOR(hos_version), HOSVER_MINOR(hos_version), HOSVER_MICRO(hos_version));
|
||||
#endif
|
||||
|
||||
/* Retrieve Exosphère API version. */
|
||||
if (!utilsGetExosphereApiVersion())
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to retrieve Exosphère API version!");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Retrieve custom firmware type. */
|
||||
_utilsGetCustomFirmwareType();
|
||||
if (g_customFirmwareType != UtilsCustomFirmwareType_Unknown) LOG_MSG_INFO("Detected %s CFW.", (g_customFirmwareType == UtilsCustomFirmwareType_Atmosphere ? "Atmosphère" : \
|
||||
(g_customFirmwareType == UtilsCustomFirmwareType_SXOS ? "SX OS" : "ReiNX")));
|
||||
|
||||
LOG_MSG_INFO("Exosphère API version info:\r\n" \
|
||||
"- Release version: %u.%u.%u.\r\n" \
|
||||
"- PKG1 key generation: %u (0x%02X).\r\n" \
|
||||
"- Target firmware: %u.%u.%u.", \
|
||||
g_exosphereApiVersion.ams_ver_major, g_exosphereApiVersion.ams_ver_minor, g_exosphereApiVersion.ams_ver_micro, \
|
||||
g_exosphereApiVersion.key_generation, !g_exosphereApiVersion.key_generation ? g_exosphereApiVersion.key_generation : (g_exosphereApiVersion.key_generation + 1), \
|
||||
g_exosphereApiVersion.target_firmware.major, g_exosphereApiVersion.target_firmware.minor, g_exosphereApiVersion.target_firmware.micro);
|
||||
|
||||
/* Get product model. */
|
||||
if (!_utilsGetProductModel()) break;
|
||||
|
||||
|
@ -210,11 +242,7 @@ bool utilsInitializeResources(void)
|
|||
if (!umsInitialize()) break;
|
||||
|
||||
/* Load keyset. */
|
||||
if (!keysLoadKeyset())
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to load keyset!\nPlease update your keys file with Lockpick_RCM.\nYou can get an updated build at: " DISCORD_SERVER_URL);
|
||||
break;
|
||||
}
|
||||
if (!keysLoadKeyset()) break;
|
||||
|
||||
/* Allocate NCA crypto buffer. */
|
||||
if (!ncaAllocateCryptoBuffer())
|
||||
|
@ -396,6 +424,21 @@ bool utilsCommitSdCardFileSystemChanges(void)
|
|||
return (g_sdCardFileSystem ? R_SUCCEEDED(fsFsCommit(g_sdCardFileSystem)) : false);
|
||||
}
|
||||
|
||||
u32 utilsGetAtmosphereVersion(void)
|
||||
{
|
||||
return MAKEHOSVERSION(g_exosphereApiVersion.ams_ver_major, g_exosphereApiVersion.ams_ver_minor, g_exosphereApiVersion.ams_ver_micro);
|
||||
}
|
||||
|
||||
u8 utilsGetAtmosphereKeyGeneration(void)
|
||||
{
|
||||
return g_exosphereApiVersion.key_generation;
|
||||
}
|
||||
|
||||
void utilsGetAtmosphereTargetFirmware(SdkAddOnVersion *out)
|
||||
{
|
||||
memcpy(out, &(g_exosphereApiVersion.target_firmware), sizeof(SdkAddOnVersion));
|
||||
}
|
||||
|
||||
u8 utilsGetCustomFirmwareType(void)
|
||||
{
|
||||
return g_customFirmwareType;
|
||||
|
@ -605,6 +648,7 @@ void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
|
|||
u8 *ptr1 = (u8*)str, *ptr2 = ptr1;
|
||||
ssize_t units = 0;
|
||||
u32 code = 0;
|
||||
bool repl = false;
|
||||
|
||||
while(cur_pos < str_size)
|
||||
{
|
||||
|
@ -612,11 +656,16 @@ void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
|
|||
if (units < 0) break;
|
||||
|
||||
if (memchr(g_illegalFileSystemChars, (int)code, g_illegalFileSystemCharsLength) || code < 0x20 || (!ascii_only && code == 0x7F) || (ascii_only && code >= 0x7F))
|
||||
{
|
||||
if (!repl)
|
||||
{
|
||||
*ptr2++ = '_';
|
||||
repl = true;
|
||||
}
|
||||
} else {
|
||||
if (ptr2 != ptr1) memmove(ptr2, ptr1, (size_t)units);
|
||||
ptr2 += units;
|
||||
repl = false;
|
||||
}
|
||||
|
||||
ptr1 += units;
|
||||
|
@ -1269,6 +1318,15 @@ static void _utilsGetLaunchPath(void)
|
|||
}
|
||||
}
|
||||
|
||||
/* SMC config item available in Atmosphère and Atmosphère-based CFWs. */
|
||||
static bool utilsGetExosphereApiVersion(void)
|
||||
{
|
||||
Result rc = splGetConfig(SplConfigItem_ExosphereApiVersion, (u64*)&g_exosphereApiVersion);
|
||||
bool ret = R_SUCCEEDED(rc);
|
||||
if (!ret) LOG_MSG_ERROR("splGetConfig failed! (0x%X).", rc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void _utilsGetCustomFirmwareType(void)
|
||||
{
|
||||
bool tx_srv = servicesCheckRunningServiceByName("tx");
|
||||
|
|
|
@ -42,7 +42,6 @@ typedef struct {
|
|||
static bool _servicesCheckInitializedServiceByName(const char *name);
|
||||
|
||||
static Result servicesAtmosphereHasService(bool *out, SmServiceName name);
|
||||
static Result servicesGetExosphereApiVersion(u32 *out);
|
||||
|
||||
static Result servicesNifmUserInitialize(void);
|
||||
static bool servicesClkGetServiceType(void *arg);
|
||||
|
@ -77,7 +76,6 @@ static u32 g_atmosphereVersion = 0;
|
|||
|
||||
/* Atmosphère-related constants. */
|
||||
static const u32 g_smAtmosphereHasService = 65100;
|
||||
static const SplConfigItem SplConfigItem_ExosphereApiVersion = (SplConfigItem)65000;
|
||||
static const u32 g_atmosphereTipcVersion = MAKEHOSVERSION(0, 19, 0);
|
||||
|
||||
bool servicesInitialize(void)
|
||||
|
@ -221,12 +219,8 @@ static Result servicesAtmosphereHasService(bool *out, SmServiceName name)
|
|||
u8 tmp = 0;
|
||||
Result rc = 0;
|
||||
|
||||
/* Get Exosphère API version. */
|
||||
if (!g_atmosphereVersion)
|
||||
{
|
||||
rc = servicesGetExosphereApiVersion(&g_atmosphereVersion);
|
||||
if (R_FAILED(rc)) LOG_MSG_ERROR("servicesGetExosphereApiVersion failed! (0x%X).", rc);
|
||||
}
|
||||
/* Get Atmosphère version if we haven't already. */
|
||||
if (!g_atmosphereVersion) g_atmosphereVersion = utilsGetAtmosphereVersion();
|
||||
|
||||
/* Check if service is running. */
|
||||
/* Dispatch IPC request using CMIF or TIPC serialization depending on our current environment. */
|
||||
|
@ -242,25 +236,6 @@ static Result servicesAtmosphereHasService(bool *out, SmServiceName name)
|
|||
return rc;
|
||||
}
|
||||
|
||||
/* SMC config item available in Atmosphère and Atmosphère-based CFWs. */
|
||||
static Result servicesGetExosphereApiVersion(u32 *out)
|
||||
{
|
||||
if (!out) return MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||
|
||||
Result rc = 0;
|
||||
u64 cfg = 0;
|
||||
u32 version = 0;
|
||||
|
||||
rc = splGetConfig(SplConfigItem_ExosphereApiVersion, &cfg);
|
||||
if (R_SUCCEEDED(rc))
|
||||
{
|
||||
*out = version = (u32)((cfg >> 40) & 0xFFFFFF);
|
||||
LOG_MSG_INFO("Exosphère API version: %u.%u.%u.", HOSVER_MAJOR(version), HOSVER_MINOR(version), HOSVER_MICRO(version));
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static Result servicesNifmUserInitialize(void)
|
||||
{
|
||||
return nifmInitialize(NifmServiceType_User);
|
||||
|
|
Loading…
Reference in a new issue