1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-23 02:36:41 +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:
Pablo Curiel 2024-04-05 23:25:32 +02:00
parent e3cd2a15eb
commit 54c5677cd0
9 changed files with 203 additions and 125 deletions

View file

@ -30,7 +30,7 @@
extern "C" { extern "C" {
#endif #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. /// Must be called (and succeed) before calling any of the functions below.
bool keysLoadKeyset(void); bool keysLoadKeyset(void);

View file

@ -85,6 +85,17 @@ FsFileSystem *utilsGetSdCardFileSystemObject(void);
/// Must be used after closing a file handle from the SD card. /// Must be used after closing a file handle from the SD card.
bool utilsCommitSdCardFileSystemChanges(void); 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. /// Returns a UtilsCustomFirmwareType value.
u8 utilsGetCustomFirmwareType(void); 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. /// 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, ...); __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. /// Replaces illegal filesystem characters in the provided NULL-terminated 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. /// 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 length can be reduced by this function. /// 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); void utilsReplaceIllegalCharacters(char *str, bool ascii_only);
/// Trims whitespace characters from the provided string. /// Trims whitespace characters from the provided string.

View file

@ -40,11 +40,11 @@ bool servicesInitialize();
/// Closes services previously initialized by servicesInitialize(). /// Closes services previously initialized by servicesInitialize().
void servicesClose(); 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); bool servicesCheckInitializedServiceByName(const char *name);
/// Checks if a service is running using its name. /// Checks if a service is running using its name, even if it wasn't initialized by this interface.
/// Wrapper for the "AtmosphereHasService" SM API extension from Atmosphère and Atmosphère-based CFWs. /// 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. /// Perfectly safe to use under development units. Not available in older Atmosphère releases.
bool servicesCheckRunningServiceByName(const char *name); bool servicesCheckRunningServiceByName(const char *name);

View file

@ -95,8 +95,8 @@
#define NRO_PATH DEVOPTAB_SDMC_DEVICE APP_BASE_PATH NRO_NAME #define NRO_PATH DEVOPTAB_SDMC_DEVICE APP_BASE_PATH NRO_NAME
#define NRO_TMP_PATH NRO_PATH ".tmp" #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 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" /* Location used by Lockpick_RCM for development 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_FILE_NAME APP_TITLE ".log"
#define LOG_BUF_SIZE 0x400000 /* 4 MiB. */ #define LOG_BUF_SIZE 0x400000 /* 4 MiB. */
@ -133,4 +133,7 @@
#define DISCORD_SERVER_URL "https://discord.gg/SCbbcQx" #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__ */ #endif /* __DEFINES_H__ */

View file

@ -1,7 +1,7 @@
{ {
"description": "Nintendo Switch Dump Tool", "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": { "dependencies": {
"header": "Dependencies", "header": "Dependencies",
@ -16,15 +16,16 @@
"header": "Acknowledgments", "header": "Acknowledgments",
"line_00": "\uE016 Switchbrew and libnx contributors.", "line_00": "\uE016 Switchbrew and libnx contributors.",
"line_01": "\uE016 SciresM, for hactool, hac2l and Atmosphère-NX.", "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_02": "\uE016 Moosehunter, for LibHac and hactoolnet.",
"line_03": "\uE016 Adubbz, for Tinfoil and its ES service bindings.", "line_03": "\uE016 shchmue, for the runtime key derivation algorithm, as well as helping with reverse engineering tasks.",
"line_04": "\uE016 RattletraPM, for the awesome icon.", "line_04": "\uE016 Adubbz, for Tinfoil and its ES service bindings.",
"line_05": "\uE016 Whovian9369, for being a key piece throughout the whole development by providing lots of testing and cool ideas.", "line_05": "\uE016 RattletraPM, for the awesome icon.",
"line_06": "\uE016 liamadvance, Shadów and SimonTime, for their tremendous help in understanding Nintendo Switch file and data formats.", "line_06": "\uE016 Whovian9369, for being a key piece throughout the whole development by providing lots of testing and cool ideas.",
"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_07": "\uE016 liamadvance, Shadów and SimonTime, for their tremendous help in understanding Nintendo Switch file and data formats.",
"line_08": "\uE016 The folks at the nxdumptool Discord server.", "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 Comfy Boyes, for always being awesome and supportive. You know who you are.", "line_09": "\uE016 The folks at the nxdumptool Discord server.",
"line_10": "\uE016 And, at last but not least, you! Thank you for using my work!" "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": { "links": {

View file

@ -62,7 +62,7 @@ namespace nxdt::views
/* Acknowledgments. */ /* Acknowledgments. */
this->addView(new brls::Header("about_tab/acknowledgments/header"_i18n)); 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 = 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. */ /* Additional links and resources. */
this->addView(new brls::Header("about_tab/links/header"_i18n)); this->addView(new brls::Header("about_tab/links/header"_i18n));

View file

@ -35,15 +35,15 @@
typedef struct { typedef struct {
///< AES-128-ECB key used to derive master KEKs from Erista master KEK sources. ///< 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]; u8 tsec_root_key[AES_128_KEY_SIZE];
///< AES-128-ECB key used to derive master KEKs from Mariko master KEK sources. ///< 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]; u8 mariko_kek[AES_128_KEY_SIZE];
///< AES-128-ECB keys used to decrypt the vast majority of Switch content. ///< 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]; u8 master_keys[NcaKeyGeneration_Max][AES_128_KEY_SIZE];
///< AES-128-XTS key needed to handle NCA header crypto. ///< 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]; 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. ///< 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. ///< 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]; 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 bool g_keysetLoaded = false;
static Mutex g_keysetMutex = 0; static Mutex g_keysetMutex = 0;
static u8 g_atmosphereKeyGeneration = 0;
static SetCalRsa2048DeviceKey g_eTicketRsaDeviceKey = {0}; static SetCalRsa2048DeviceKey g_eTicketRsaDeviceKey = {0};
static KeysNxKeyset g_nxKeyset = {0}; static KeysNxKeyset g_nxKeyset = {0};
static bool g_latestMasterKeyAvailable = false; static bool g_tsecRootKeyAvailable = false, g_marikoKekAvailable = false;
static bool g_wipedSetCal = false; static bool g_wipedSetCal = false;
@ -126,6 +128,10 @@ bool keysLoadKeyset(void)
ret = g_keysetLoaded; ret = g_keysetLoaded;
if (ret) break; 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. */ /* Get eTicket RSA device key. */
Result rc = setcalGetEticketDeviceKey(&g_eTicketRsaDeviceKey); Result rc = setcalGetEticketDeviceKey(&g_eTicketRsaDeviceKey);
if (R_FAILED(rc)) if (R_FAILED(rc))
@ -134,15 +140,11 @@ bool keysLoadKeyset(void)
break; break;
} }
/* Read data from the Lockpick_RCM keys file. */ /* Read data from the keys file. */
if (!keysReadKeysFromFile()) break; if (!keysReadKeysFromFile()) break;
/* Derive master keys. */ /* Derive master keys. */
if (!keysDeriveMasterKeys()) if (!keysDeriveMasterKeys()) break;
{
LOG_MSG_ERROR("Failed to derive master keys!");
break;
}
/* Derive NCA header key. */ /* Derive NCA header key. */
if (!keysDeriveNcaHeaderKey()) break; if (!keysDeriveNcaHeaderKey()) break;
@ -187,7 +189,7 @@ const u8 *keysGetNcaHeaderKey(void)
const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation) const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation)
{ {
const u8 *ret = NULL; 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) if (kaek_index >= NcaKeyAreaEncryptionKeyIndex_Count)
{ {
@ -205,11 +207,11 @@ const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation)
{ {
if (!g_keysetLoaded) break; 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)) 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; ret = NULL;
} }
} }
@ -257,7 +259,7 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o
const u8 *keysGetTicketCommonKey(u8 key_generation) const u8 *keysGetTicketCommonKey(u8 key_generation)
{ {
const u8 *ret = NULL; 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) if (key_generation > NcaKeyGeneration_Max)
{ {
@ -269,11 +271,11 @@ const u8 *keysGetTicketCommonKey(u8 key_generation)
{ {
if (!g_keysetLoaded) break; 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)) 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; ret = NULL;
} }
} }
@ -543,10 +545,10 @@ static bool keysReadKeysFromFile(void)
char test_name[0x40] = {0}; char test_name[0x40] = {0};
const char *keys_file_path = (utilsIsDevelopmentUnit() ? DEV_KEYS_FILE_PATH : PROD_KEYS_FILE_PATH); const char *keys_file_path = (utilsIsDevelopmentUnit() ? DEV_KEYS_FILE_PATH : PROD_KEYS_FILE_PATH);
const bool is_mariko = utilsIsMarikoUnit();
bool is_mariko = utilsIsMarikoUnit(); bool eticket_rsa_kek_available = false;
bool tsec_root_key_available = false, mariko_kek_available = false; const char *eticket_rsa_kek_name = (g_eTicketRsaDeviceKey.generation > 0 ? "eticket_rsa_kek_personalized" : "eticket_rsa_kek");
bool use_personalized_eticket_rsa_kek = (g_eTicketRsaDeviceKey.generation > 0), eticket_rsa_kek_available = false;
keys_file = fopen(keys_file_path, "rb"); keys_file = fopen(keys_file_path, "rb");
if (!keys_file) if (!keys_file)
@ -579,16 +581,16 @@ static bool keysReadKeysFromFile(void)
{ {
/* Parse Mariko KEK. */ /* Parse Mariko KEK. */
/* This will only appear on Mariko units. */ /* 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 { } else {
/* Parse TSEC root key. */ /* Parse TSEC root key. */
/* This will only appear on Erista units. */ /* 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. */ /* The personalized entry only appears on consoles that use the new PRODINFO key generation scheme. */
if (!eticket_rsa_kek_available) 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. */ /* 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", i, g_nxKeyset.master_keys[i], break);
PARSE_HEX_KEY_WITH_INDEX("master_key", key_gen_val, g_nxKeyset.master_keys[key_gen_val], break);
} }
} }
@ -624,32 +625,10 @@ static bool keysReadKeysFromFile(void)
return false; return false;
} }
/* Check if the latest master key was retrieved. */ /* Bail out if we couldn't retrieve the eTicket RSA KEK. */
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;
}
}
}
if (!eticket_rsa_kek_available) 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; return false;
} }
@ -659,34 +638,87 @@ static bool keysReadKeysFromFile(void)
static bool keysDeriveMasterKeys(void) static bool keysDeriveMasterKeys(void)
{ {
u8 tmp[AES_128_KEY_SIZE] = {0}; u8 tmp[AES_128_KEY_SIZE] = {0};
const u8 latest_mkey_index = (NcaKeyGeneration_Current - 1); const u8 current_mkey_index = (NcaKeyGeneration_Current - 1); // Convert into an index.
bool is_dev = utilsIsDevelopmentUnit(); 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. */ /* 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. */ /* 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); aes128EcbCrypt(tmp, is_dev ? g_marikoMasterKekSourceDev : g_marikoMasterKekSourceProd, g_nxKeyset.mariko_kek, false);
} else { } 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. */ /* 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); 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. */ /* 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. */ /* 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); g_nxKeyset.master_keys[i], false);
/* Check if we derived the right keys. */ /* Check if we derived the right keys. */
aes128EcbCrypt(tmp, is_dev ? g_masterKeyVectorsDev[NcaKeyGeneration_Since100NUP] : g_masterKeyVectorsProd[NcaKeyGeneration_Since100NUP], \ aes128EcbCrypt(tmp, is_dev ? g_masterKeyVectorsDev[NcaKeyGeneration_Since100NUP] : g_masterKeyVectorsProd[NcaKeyGeneration_Since100NUP], \
g_nxKeyset.master_keys[NcaKeyGeneration_Since100NUP], false); 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) static bool keysDeriveNcaHeaderKey(void)
@ -729,21 +761,21 @@ static bool keysDerivePerGenerationKeys(void)
for(u8 i = 1; i <= NcaKeyGeneration_Max; i++) 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. */ /* 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; continue;
} }
/* Derive NCA key area keys for this generation. */ /* Derive NCA key area keys for this generation. */
for(u8 j = 0; j < NcaKeyAreaEncryptionKeyIndex_Count; j++) 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; success = false;
break; break;
} }
@ -752,7 +784,7 @@ static bool keysDerivePerGenerationKeys(void)
if (!success) break; if (!success) break;
/* Derive ticket common key for this generation. */ /* 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; 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. */ /* 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) 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); const bool is_device_unique = (option.fields.is_device_unique == 1);
u8 key_type_idx = option.fields.key_type_idx; 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);
u8 seal_key_idx = option.fields.seal_key_idx;
if (!kek_src || key_generation > NcaKeyGeneration_Max || is_device_unique || key_type_idx >= SmcKeyType_Count || seal_key_idx >= SmcSealKey_Count || \ 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) option.fields.reserved != 0 || !out_kek)
@ -856,15 +887,13 @@ static bool keysGenerateAesKek(const u8 *kek_src, u8 key_generation, SmcGenerate
return false; return false;
} }
if (key_generation) key_generation--;
u8 kekek_src[AES_128_KEY_SIZE] = {0}, kekek[AES_128_KEY_SIZE] = {0}; 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. */ /* Make sure this master key is available. */
if (keysIsKeyEmpty(mkey)) if (keysIsKeyEmpty(mkey))
{ {
LOG_MSG_ERROR("Master key %02X unavailable!", key_generation); LOG_MSG_ERROR("\"master_key_%02X\" unavailable!", mkey_index);
return false; return false;
} }

View file

@ -41,6 +41,17 @@
/* Type definitions. */ /* 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 { typedef struct {
u32 major; u32 major;
u32 minor; u32 minor;
@ -97,10 +108,16 @@ static const size_t g_outputDirsCount = MAX_ELEMENTS(g_outputDirs);
static bool g_appUpdated = false; static bool g_appUpdated = false;
static const SplConfigItem SplConfigItem_ExosphereApiVersion = (SplConfigItem)65000;
static UtilsExosphereApiVersion g_exosphereApiVersion = {0};
/* Function prototypes. */ /* Function prototypes. */
static void _utilsGetLaunchPath(void); static void _utilsGetLaunchPath(void);
static bool utilsGetExosphereApiVersion(void);
static void _utilsGetCustomFirmwareType(void); static void _utilsGetCustomFirmwareType(void);
static bool _utilsGetProductModel(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)); LOG_MSG_INFO("Horizon OS version: %u.%u.%u.", HOSVER_MAJOR(hos_version), HOSVER_MINOR(hos_version), HOSVER_MICRO(hos_version));
#endif #endif
/* Retrieve Exosphère API version. */
if (!utilsGetExosphereApiVersion())
{
LOG_MSG_ERROR("Failed to retrieve Exosphère API version!");
break;
}
/* Retrieve custom firmware type. */ /* Retrieve custom firmware type. */
_utilsGetCustomFirmwareType(); _utilsGetCustomFirmwareType();
if (g_customFirmwareType != UtilsCustomFirmwareType_Unknown) LOG_MSG_INFO("Detected %s CFW.", (g_customFirmwareType == UtilsCustomFirmwareType_Atmosphere ? "Atmosphère" : \ if (g_customFirmwareType != UtilsCustomFirmwareType_Unknown) LOG_MSG_INFO("Detected %s CFW.", (g_customFirmwareType == UtilsCustomFirmwareType_Atmosphere ? "Atmosphère" : \
(g_customFirmwareType == UtilsCustomFirmwareType_SXOS ? "SX OS" : "ReiNX"))); (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. */ /* Get product model. */
if (!_utilsGetProductModel()) break; if (!_utilsGetProductModel()) break;
@ -210,11 +242,7 @@ bool utilsInitializeResources(void)
if (!umsInitialize()) break; if (!umsInitialize()) break;
/* Load keyset. */ /* Load keyset. */
if (!keysLoadKeyset()) if (!keysLoadKeyset()) break;
{
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;
}
/* Allocate NCA crypto buffer. */ /* Allocate NCA crypto buffer. */
if (!ncaAllocateCryptoBuffer()) if (!ncaAllocateCryptoBuffer())
@ -396,6 +424,21 @@ bool utilsCommitSdCardFileSystemChanges(void)
return (g_sdCardFileSystem ? R_SUCCEEDED(fsFsCommit(g_sdCardFileSystem)) : false); 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) u8 utilsGetCustomFirmwareType(void)
{ {
return g_customFirmwareType; return g_customFirmwareType;
@ -605,6 +648,7 @@ void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
u8 *ptr1 = (u8*)str, *ptr2 = ptr1; u8 *ptr1 = (u8*)str, *ptr2 = ptr1;
ssize_t units = 0; ssize_t units = 0;
u32 code = 0; u32 code = 0;
bool repl = false;
while(cur_pos < str_size) while(cur_pos < str_size)
{ {
@ -612,11 +656,16 @@ void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
if (units < 0) break; if (units < 0) break;
if (memchr(g_illegalFileSystemChars, (int)code, g_illegalFileSystemCharsLength) || code < 0x20 || (!ascii_only && code == 0x7F) || (ascii_only && code >= 0x7F)) if (memchr(g_illegalFileSystemChars, (int)code, g_illegalFileSystemCharsLength) || code < 0x20 || (!ascii_only && code == 0x7F) || (ascii_only && code >= 0x7F))
{
if (!repl)
{ {
*ptr2++ = '_'; *ptr2++ = '_';
repl = true;
}
} else { } else {
if (ptr2 != ptr1) memmove(ptr2, ptr1, (size_t)units); if (ptr2 != ptr1) memmove(ptr2, ptr1, (size_t)units);
ptr2 += units; ptr2 += units;
repl = false;
} }
ptr1 += units; 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) static void _utilsGetCustomFirmwareType(void)
{ {
bool tx_srv = servicesCheckRunningServiceByName("tx"); bool tx_srv = servicesCheckRunningServiceByName("tx");

View file

@ -42,7 +42,6 @@ typedef struct {
static bool _servicesCheckInitializedServiceByName(const char *name); static bool _servicesCheckInitializedServiceByName(const char *name);
static Result servicesAtmosphereHasService(bool *out, SmServiceName name); static Result servicesAtmosphereHasService(bool *out, SmServiceName name);
static Result servicesGetExosphereApiVersion(u32 *out);
static Result servicesNifmUserInitialize(void); static Result servicesNifmUserInitialize(void);
static bool servicesClkGetServiceType(void *arg); static bool servicesClkGetServiceType(void *arg);
@ -77,7 +76,6 @@ static u32 g_atmosphereVersion = 0;
/* Atmosphère-related constants. */ /* Atmosphère-related constants. */
static const u32 g_smAtmosphereHasService = 65100; static const u32 g_smAtmosphereHasService = 65100;
static const SplConfigItem SplConfigItem_ExosphereApiVersion = (SplConfigItem)65000;
static const u32 g_atmosphereTipcVersion = MAKEHOSVERSION(0, 19, 0); static const u32 g_atmosphereTipcVersion = MAKEHOSVERSION(0, 19, 0);
bool servicesInitialize(void) bool servicesInitialize(void)
@ -221,12 +219,8 @@ static Result servicesAtmosphereHasService(bool *out, SmServiceName name)
u8 tmp = 0; u8 tmp = 0;
Result rc = 0; Result rc = 0;
/* Get Exosphère API version. */ /* Get Atmosphère version if we haven't already. */
if (!g_atmosphereVersion) if (!g_atmosphereVersion) g_atmosphereVersion = utilsGetAtmosphereVersion();
{
rc = servicesGetExosphereApiVersion(&g_atmosphereVersion);
if (R_FAILED(rc)) LOG_MSG_ERROR("servicesGetExosphereApiVersion failed! (0x%X).", rc);
}
/* Check if service is running. */ /* Check if service is running. */
/* Dispatch IPC request using CMIF or TIPC serialization depending on our current environment. */ /* 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; 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) static Result servicesNifmUserInitialize(void)
{ {
return nifmInitialize(NifmServiceType_User); return nifmInitialize(NifmServiceType_User);