diff --git a/include/core/keys.h b/include/core/keys.h index fb53de1..24e7e05 100644 --- a/include/core/keys.h +++ b/include/core/keys.h @@ -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); diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index 39bf451..d36a574 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -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. diff --git a/include/core/services.h b/include/core/services.h index e95d7f8..964a130 100644 --- a/include/core/services.h +++ b/include/core/services.h @@ -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); diff --git a/include/defines.h b/include/defines.h index 5b9c3b9..f841ded 100644 --- a/include/defines.h +++ b/include/defines.h @@ -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__ */ diff --git a/romfs/i18n/en-US/about_tab.json b/romfs/i18n/en-US/about_tab.json index f75d349..a695ddc 100644 --- a/romfs/i18n/en-US/about_tab.json +++ b/romfs/i18n/en-US/about_tab.json @@ -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": { diff --git a/source/about_tab.cpp b/source/about_tab.cpp index 01202f6..cc7ad4e 100644 --- a/source/about_tab.cpp +++ b/source/about_tab.cpp @@ -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)); diff --git a/source/core/keys.c b/source/core/keys.c index b2777cc..7afce76 100644 --- a/source/core/keys.c +++ b/source/core/keys.c @@ -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; } diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 3ede30d..83d3a1d 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -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) { @@ -613,10 +657,15 @@ void utilsReplaceIllegalCharacters(char *str, bool ascii_only) if (memchr(g_illegalFileSystemChars, (int)code, g_illegalFileSystemCharsLength) || code < 0x20 || (!ascii_only && code == 0x7F) || (ascii_only && code >= 0x7F)) { - *ptr2++ = '_'; + 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"); diff --git a/source/core/services.c b/source/core/services.c index 36f74fe..bde822a 100644 --- a/source/core/services.c +++ b/source/core/services.c @@ -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);