1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-12-23 00:52:10 +00:00

keys: relax mkey requirements on older firmwares.

A hardcoded table with HOS version numbers and master key indexes is now used to determine the HOS key generation at runtime, whenever possible. This allows the application to more accurately determine the key generation that's actually required by the console it's running on.

Most parts of the code that relied on the Atmosphère key generation value have been updated to use the HOS key generation value instead. If the HOS version is too high/unknown, the code will fallback to the Atmosphère key generation value.

Furthermore, if the HOS key generation value is lower than our last known key generation, the code will now try to look for the highest available master key it can use to derive all lower master keys, beginning with the last known master key and ending with the master key that matches the HOS key generation value. Previous behavior only checked the availability of the master key that matched the Atmosphère key generation, which isn't completely reliable nor accurate.

If this process fails, current master key derivation will be carried out as a last resort, which wasn't being done either under this specific scenario.

Other changes include:

* keys: add keysGetHorizonOsKeyGeneration().
* keys: move current master key derivation logic into its own function, keysDeriveCurrentMasterKey(), which is now used if both Atmosphère and HOS and up-to-date, or if a lower master key is required (as a last resort method).
This commit is contained in:
Pablo Curiel 2024-10-12 20:38:33 +02:00
parent 94c396af19
commit f376eb6db4
3 changed files with 131 additions and 67 deletions

View file

@ -56,7 +56,7 @@ static const u8 g_masterKeyVectorsProd[NcaKeyGeneration_Current][AES_128_KEY_SIZ
{ 0xAF, 0x11, 0x4C, 0x67, 0x17, 0x7A, 0x52, 0x43, 0xF7, 0x70, 0x2F, 0xC7, 0xEF, 0x81, 0x72, 0x16 }, ///< Master key 0E encrypted with master key 0F.
{ 0x25, 0x12, 0x8B, 0xCB, 0xB5, 0x46, 0xA1, 0xF8, 0xE0, 0x52, 0x15, 0xB7, 0x0B, 0x57, 0x00, 0xBD }, ///< Master key 0F encrypted with master key 10.
{ 0x58, 0x15, 0xD2, 0xF6, 0x8A, 0xE8, 0x19, 0xAB, 0xFB, 0x2D, 0x52, 0x9D, 0xE7, 0x55, 0xF3, 0x93 }, ///< Master key 10 encrypted with master key 11.
{ 0x4A, 0x01, 0x3B, 0xC7, 0x44, 0x6E, 0x45, 0xBD, 0xE6, 0x5E, 0x2B, 0xEC, 0x07, 0x37, 0x52, 0x86 }, ///< Master key 11 encrypted with master key 12.
{ 0x4A, 0x01, 0x3B, 0xC7, 0x44, 0x6E, 0x45, 0xBD, 0xE6, 0x5E, 0x2B, 0xEC, 0x07, 0x37, 0x52, 0x86 } ///< Master key 11 encrypted with master key 12.
};
/* Used to derive all previous master keys using the latest master key on development units. */
@ -80,7 +80,7 @@ static const u8 g_masterKeyVectorsDev[NcaKeyGeneration_Current][AES_128_KEY_SIZE
{ 0x78, 0x66, 0x19, 0xBD, 0x86, 0xE7, 0xC1, 0x09, 0x9B, 0x6F, 0x92, 0xB2, 0x58, 0x7D, 0xCF, 0x26 }, ///< Master key 0E encrypted with master key 0F.
{ 0x39, 0x1E, 0x7E, 0xF8, 0x7E, 0x73, 0xEA, 0x6F, 0xAF, 0x00, 0x3A, 0xB4, 0xAA, 0xB8, 0xB7, 0x59 }, ///< Master key 0F encrypted with master key 10.
{ 0x0C, 0x75, 0x39, 0x15, 0x53, 0xEA, 0x81, 0x11, 0xA3, 0xE0, 0xDC, 0x3D, 0x0E, 0x76, 0xC6, 0xB8 }, ///< Master key 10 encrypted with master key 11.
{ 0x90, 0x64, 0xF9, 0x08, 0x29, 0x88, 0xD4, 0xDC, 0x73, 0xA4, 0xA1, 0x13, 0x9E, 0x59, 0x85, 0xA0 }, ///< Master key 11 encrypted with master key 12.
{ 0x90, 0x64, 0xF9, 0x08, 0x29, 0x88, 0xD4, 0xDC, 0x73, 0xA4, 0xA1, 0x13, 0x9E, 0x59, 0x85, 0xA0 } ///< Master key 11 encrypted with master key 12.
};
/* Used to derive a master KEK using the TSEC root key on Erista units. */
@ -134,7 +134,7 @@ static const u8 g_smcKeyTypeSources[SmcKeyType_Count][AES_128_KEY_SIZE] = {
[SmcKeyType_Default] = { 0x4D, 0x87, 0x09, 0x86, 0xC4, 0x5D, 0x20, 0x72, 0x2F, 0xBA, 0x10, 0x53, 0xDA, 0x92, 0xE8, 0xA9 }, ///< Also known as "aes_kek_generation_source".
[SmcKeyType_NormalOnly] = { 0x25, 0x03, 0x31, 0xFB, 0x25, 0x26, 0x0B, 0x79, 0x8C, 0x80, 0xD2, 0x69, 0x98, 0xE2, 0x22, 0x77 },
[SmcKeyType_RecoveryOnly] = { 0x76, 0x14, 0x1D, 0x34, 0x93, 0x2D, 0xE1, 0x84, 0x24, 0x7B, 0x66, 0x65, 0x55, 0x04, 0x65, 0x81 },
[SmcKeyType_NormalAndRecovery] = { 0xAF, 0x3D, 0xB7, 0xF3, 0x08, 0xA2, 0xD8, 0xA2, 0x08, 0xCA, 0x18, 0xA8, 0x69, 0x46, 0xC9, 0x0B },
[SmcKeyType_NormalAndRecovery] = { 0xAF, 0x3D, 0xB7, 0xF3, 0x08, 0xA2, 0xD8, 0xA2, 0x08, 0xCA, 0x18, 0xA8, 0x69, 0x46, 0xC9, 0x0B }
};
/* Used by GenerateAesKek to derive keys. Found in TrustZone / Secure Monitor. */

View file

@ -84,13 +84,16 @@ NXDT_ASSERT(EticketRsaDeviceKey, 0x240);
/* Function prototypes. */
static bool keysIsKeyEmpty(const void *key);
NX_INLINE u8 keysGetHorizonOsKeyGeneration(void);
NX_INLINE bool keysIsKeyEmpty(const void *key);
static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **value);
static bool keysParseHexKey(u8 *out, size_t out_size, const char *key, const char *value);
static bool keysReadKeysFromFile(void);
static bool keysDeriveMasterKeys(void);
static bool keysDeriveCurrentMasterKey(void);
static bool keysDeriveNcaHeaderKey(void);
static bool keysDerivePerGenerationKeys(void);
static bool keysDeriveGcCardInfoKey(void);
@ -110,7 +113,30 @@ 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, g_currentMasterKeyIndex = 0;
/* TODO: update on master key changes. */
static const u32 g_hosMasterKeyIndexTable[NcaKeyGeneration_Current] = {
[NcaKeyGeneration_Since100NUP] = 0,
[NcaKeyGeneration_Since300NUP - 1] = MAKEHOSVERSION(3, 0, 0),
[NcaKeyGeneration_Since301NUP - 1] = MAKEHOSVERSION(3, 0, 1),
[NcaKeyGeneration_Since400NUP - 1] = MAKEHOSVERSION(4, 0, 0),
[NcaKeyGeneration_Since500NUP - 1] = MAKEHOSVERSION(5, 0, 0),
[NcaKeyGeneration_Since600NUP - 1] = MAKEHOSVERSION(6, 0, 0),
[NcaKeyGeneration_Since620NUP - 1] = MAKEHOSVERSION(6, 2, 0),
[NcaKeyGeneration_Since700NUP - 1] = MAKEHOSVERSION(7, 0, 0),
[NcaKeyGeneration_Since810NUP - 1] = MAKEHOSVERSION(8, 1, 0),
[NcaKeyGeneration_Since900NUP - 1] = MAKEHOSVERSION(9, 0, 0),
[NcaKeyGeneration_Since910NUP - 1] = MAKEHOSVERSION(9, 1, 0),
[NcaKeyGeneration_Since1210NUP - 1] = MAKEHOSVERSION(12, 1, 0),
[NcaKeyGeneration_Since1300NUP - 1] = MAKEHOSVERSION(13, 0, 0),
[NcaKeyGeneration_Since1400NUP - 1] = MAKEHOSVERSION(14, 0, 0),
[NcaKeyGeneration_Since1500NUP - 1] = MAKEHOSVERSION(15, 0, 0),
[NcaKeyGeneration_Since1600NUP - 1] = MAKEHOSVERSION(16, 0, 0),
[NcaKeyGeneration_Since1700NUP - 1] = MAKEHOSVERSION(17, 0, 0),
[NcaKeyGeneration_Since1800NUP - 1] = MAKEHOSVERSION(18, 0, 0),
[NcaKeyGeneration_Since1900NUP - 1] = MAKEHOSVERSION(19, 0, 0)
};
static u8 g_atmosphereKeyGeneration = 0, g_currentMasterKeyIndex = 0, g_hosKeyGeneration = 0;
static bool g_outdatedMasterKeyVectors = false, g_lowMasterKeyRequirement = false;
static SetCalRsa2048DeviceKey g_eTicketRsaDeviceKey = {0};
@ -136,11 +162,18 @@ bool keysLoadKeyset(void)
/* Get current master key index. */
g_currentMasterKeyIndex = (NcaKeyGeneration_Current - 1);
/* Determine if our master key vectors are outdated. */
g_outdatedMasterKeyVectors = (g_atmosphereKeyGeneration > g_currentMasterKeyIndex);
/* Get Horizon OS key generation. This also represents an index. */
/* If needed, we'll manually adjust it -- it shall never exceed Atmosphère's key generation, for obvious reasons. */
g_hosKeyGeneration = keysGetHorizonOsKeyGeneration();
if (g_hosKeyGeneration > g_atmosphereKeyGeneration) g_hosKeyGeneration = g_atmosphereKeyGeneration;
/* Determine if we're dealing with a lower master key requirement. */
g_lowMasterKeyRequirement = (g_atmosphereKeyGeneration < g_currentMasterKeyIndex);
g_lowMasterKeyRequirement = (g_hosKeyGeneration < g_currentMasterKeyIndex);
/* Determine if our master key vectors are outdated. */
g_outdatedMasterKeyVectors = (!g_lowMasterKeyRequirement && g_atmosphereKeyGeneration > g_currentMasterKeyIndex);
LOG_MSG_DEBUG("AMS key generation: %02X | Last known master key index: %02X | HOS key generation: %02X.", g_atmosphereKeyGeneration, g_currentMasterKeyIndex, g_hosKeyGeneration);
/* Get eTicket RSA device key. */
Result rc = setcalGetEticketDeviceKey(&g_eTicketRsaDeviceKey);
@ -176,10 +209,8 @@ bool keysLoadKeyset(void)
ret = g_keysetLoaded = true;
}
#if LOG_LEVEL == LOG_LEVEL_DEBUG
LOG_DATA_DEBUG(&g_eTicketRsaDeviceKey, sizeof(SetCalRsa2048DeviceKey), "eTicket RSA device key dump:");
LOG_DATA_DEBUG(&g_nxKeyset, sizeof(KeysNxKeyset), "NX keyset dump:");
#endif
//LOG_DATA_DEBUG(&g_eTicketRsaDeviceKey, sizeof(SetCalRsa2048DeviceKey), "eTicket RSA device key dump:");
//LOG_DATA_DEBUG(&g_nxKeyset, sizeof(KeysNxKeyset), "NX keyset dump:");
return ret;
}
@ -306,7 +337,23 @@ const u8 *keysGetGameCardInfoKey(void)
return ret;
}
static bool keysIsKeyEmpty(const void *key)
NX_INLINE u8 keysGetHorizonOsKeyGeneration(void)
{
u32 version = hosversionGet();
/* Short-circuit: return NcaKeyGeneration_Max if we're running under a HOS version we don't know about. */
if (version > g_hosMasterKeyIndexTable[NcaKeyGeneration_Current - 1]) return NcaKeyGeneration_Max;
/* Look for a matching HOS version entry and return its index as the master key generation. */
for(u8 i = (NcaKeyGeneration_Current - 1); i > NcaKeyGeneration_Since100NUP; i--)
{
if (version >= g_hosMasterKeyIndexTable[i]) return i;
}
return NcaKeyGeneration_Since100NUP;
}
NX_INLINE bool keysIsKeyEmpty(const void *key)
{
const u8 null_key[AES_128_KEY_SIZE] = {0};
return (memcmp(key, null_key, AES_128_KEY_SIZE) == 0);
@ -612,7 +659,7 @@ static bool keysReadKeysFromFile(void)
}
/* Parse master keys, starting with the minimum required one (if dealing with a lower master key requirement) or the last known one. */
for(u8 i = (g_lowMasterKeyRequirement ? g_atmosphereKeyGeneration : g_currentMasterKeyIndex); i < NcaKeyGeneration_Max; i++)
for(u8 i = (g_lowMasterKeyRequirement ? g_hosKeyGeneration : g_currentMasterKeyIndex); i < NcaKeyGeneration_Max; i++)
{
PARSE_HEX_KEY_WITH_INDEX("master_key", i, g_nxKeyset.master_keys[i], break);
}
@ -648,15 +695,13 @@ static bool keysReadKeysFromFile(void)
static bool keysDeriveMasterKeys(void)
{
u8 tmp[AES_128_KEY_SIZE] = {0}, current_mkey_index = g_currentMasterKeyIndex;
const bool is_dev = utilsIsDevelopmentUnit(), is_mariko = utilsIsMarikoUnit();
const bool is_dev = utilsIsDevelopmentUnit();
bool current_mkey_available = false;
if (current_mkey_index != g_atmosphereKeyGeneration) LOG_MSG_WARNING("Current key generation mismatch detected (%02X != %02X).", current_mkey_index, g_atmosphereKeyGeneration);
if (g_outdatedMasterKeyVectors)
{
/* Our master key vectors are outdated. */
/* This means the user is running both a HOS version with a newer master key generation and an Atmosphère release with support for said HOS version. */
/* This means the console is running both a HOS version with a newer master 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. */
current_mkey_available = true;
@ -680,59 +725,37 @@ static bool keysDeriveMasterKeys(void)
g_atmosphereKeyGeneration, current_mkey_index, PRERELEASE_URL, DISCORD_SERVER_URL);
return false;
}
} else {
} else
if (g_lowMasterKeyRequirement)
{
/* Our master key vectors are up-to-date. */
/* However, we may also be running under a console with an older HOS version and a lower master key generation. */
/* If this is the case, we'll need to adjust the current master key index to match Atmosphére's key generation value. */
/* However, we are running under a console with an older HOS version and a lower master key generation. */
/* There really is no point in demanding the most up-to-date master key under lower firmware versions. */
if (g_lowMasterKeyRequirement) current_mkey_index = g_atmosphereKeyGeneration;
/* Now then, just checking if we have the current master key should suffice. */
current_mkey_available = !keysIsKeyEmpty(g_nxKeyset.master_keys[current_mkey_index]);
/* Bail out if we're dealing with a lower master key generation and we don't have its master key. */
/* This is because current master key derivation depends on generation-specific master KEKs, and we only hardcode the last known one. */
if (!current_mkey_available && g_lowMasterKeyRequirement)
/* In other words, we'll need to adjust the current master key index. We'll just look for the highest available master key we can use. */
for(u8 i = current_mkey_index; i >= g_hosKeyGeneration; i--)
{
LOG_MSG_ERROR("\"master_key_%02x\" unavailable! Unable to derive lower\r\n" \
"master keys. Please redump your console keys and try again.", current_mkey_index);
if (!keysIsKeyEmpty(g_nxKeyset.master_keys[i]))
{
current_mkey_index = i;
current_mkey_available = true;
break;
}
}
/* Try to derive the current master key as a last resort if we couldn't find a valid master key. */
/* If that fails too, we'll just bail out. */
if (!current_mkey_available && !keysDeriveCurrentMasterKey())
{
LOG_MSG_ERROR("HOS key generation (%02X) is lower than the last known\r\n" \
"key generation (%02X). Furthermore, none of the master keys within that\r\n" \
"range was available in the keys file. Current master key derivation\r\n" \
"also failed. Please redump your console keys and try again.", g_hosKeyGeneration, current_mkey_index);
return false;
}
}
/* Derive current master key if it's not populated. */
/* We should only enter this conditional block if Atmosphère's key generation matches our last known master key generation. */
if (!current_mkey_available)
{
LOG_MSG_WARNING("Current master key (%02X) unavailable. It will be derived.", current_mkey_index);
/* Derive the current master KEK. */
if (is_mariko)
{
if (!g_marikoKekAvailable)
{
LOG_MSG_ERROR("\"mariko_kek\" unavailable! Unable to derive current\r\n" \
"master key. You may need to manually derive it using PartialAesKeyCrack,\r\n" \
"and/or redump your console keys. Please try again afterwards.");
return false;
}
/* Derive the current 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\r\n" \
"current master key. Please redump your console keys and try again.", TSEC_ROOT_KEY_VERSION);
return false;
}
/* Derive the current 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 current master key using the hardcoded master key source and the current master KEK. */
aes128EcbCrypt(g_nxKeyset.master_keys[current_mkey_index], g_masterKeySource, tmp, false);
} else {
/* Our master key vectors are up-to-date and we're running under an up-to-date Atmosphère build / HOS version. */
/* We'll just try to derive the current master key -- if it's already available, this will return true immediately. */
if (!keysDeriveCurrentMasterKey()) return false;
}
/* Derive all lower master keys using the current master key and the master key vectors. */
@ -749,6 +772,47 @@ static bool keysDeriveMasterKeys(void)
return ret;
}
static bool keysDeriveCurrentMasterKey(void)
{
u8 master_kek[AES_128_KEY_SIZE] = {0};
const bool is_dev = utilsIsDevelopmentUnit(), is_mariko = utilsIsMarikoUnit();
/* Make sure we don't already have the current master key. */
if (!keysIsKeyEmpty(g_nxKeyset.master_keys[g_currentMasterKeyIndex])) return true;
LOG_MSG_WARNING("Current master key (%02X) unavailable. It will be derived.", g_currentMasterKeyIndex);
/* Derive the current master KEK. */
if (is_mariko)
{
if (!g_marikoKekAvailable)
{
LOG_MSG_ERROR("\"mariko_kek\" unavailable! Unable to derive current\r\n" \
"master key. You may need to manually derive it using PartialAesKeyCrack,\r\n" \
"and/or redump your console keys. Please try again afterwards.");
return false;
}
/* Derive the current master KEK using the hardcoded Mariko master KEK source and the Mariko KEK. */
aes128EcbCrypt(master_kek, 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\r\n" \
"current master key. Please redump your console keys and try again.", TSEC_ROOT_KEY_VERSION);
return false;
}
/* Derive the current master KEK using the hardcoded Erista master KEK source and the TSEC root key. */
aes128EcbCrypt(master_kek, g_eristaMasterKekSource, g_nxKeyset.tsec_root_key, false);
}
/* Derive the current master key using the hardcoded master key source and the current master KEK. */
aes128EcbCrypt(g_nxKeyset.master_keys[g_currentMasterKeyIndex], g_masterKeySource, master_kek, false);
return true;
}
static bool keysDeriveNcaHeaderKey(void)
{
u8 nca_header_kek[AES_128_KEY_SIZE] = {0};

View file

@ -201,7 +201,7 @@ bool utilsInitializeResources(void)
"- Horizon OS version: %u.%u.%u.\r\n" \
"- CFW: %s.\r\n" \
"- eMMC type: %s.\r\n" \
"- SoC type: %s\r\n" \
"- SoC type: %s.\r\n" \
"- Development unit: %s.\r\n" \
"- Terra flag: %s.\r\n" \
"- Execution mode: %s.", \