From f79680184d143cefa901479b3a5b4b3ed19ad7b1 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sat, 8 Apr 2023 13:34:53 +0200 Subject: [PATCH] Runtime key derivation with hardcoded key sources * aes: add aes128EcbCrypt() as a one-shot function to perform AES-128-ECB crypto. The rest of the codebase now calls this function whenever suitable. * fs_ext: add const keyword to IPC input structs wherever suitable. * key_sources: add hardcoded master key vectors (prod, dev); master KEK sources (Erista, Mariko); master key source; ticket common key source; SMC key type sources; SMC seal key masks; AES key generation source; NCA header KEK source; NCA header key source and NCA KAEK sources. Also fixed the hardcoded gamecard CardInfo key source for dev units (it was previously generated using retail keydata, my bad). * keys: remove keysGetNcaMainSignatureModulus(); remove keysDecryptNcaKeyAreaEntry(); repurpose keyset struct to only hold keys that can actually be used for the current hardware type; remove KeysGameCardKeyset; remove keysIsXXModulusYYMandatory() helpers; remove keysRetrieveKeysFromProgramMemory(); remove keysDeriveSealedNcaKeyAreaEncryptionKeys(); add keysDeriveMasterKeys() and keysDerivePerGenerationKeys(); rename keysDeriveGameCardKeys() -> keysDeriveGcCardInfoKey(); add small reimplementations of GenerateAesKek, LoadAesKey and GenerateAesKey; add keysLoadAesKeyFromAesKek() and keysGenerateAesKeyFromAesKek() wrappers. Furthermore, master key derivation is now carried out manually using hardcoded key sources and the last known master key, which is loaded from the Lockpick_RCM keys file -- if the last known master key is unavailable, the key derivation algorithm will then fallback to TSEC root key / Mariko KEK based key derivation, depending on the hardware type. * nca: add hardcoded NCA man signature moduli (prod, dev); merge ncaDecryptKeyArea() and ncaEncryptKeyArea() into ncaKeyAreaCrypt(). * nxdt_utils: add utilsIsMarikoUnit(); remove _utilsAppletModeCheck(); rename utilsAppletModeCheck() -> utilsIsAppletMode(). * services: remove spl:mig dependency (yay). * smc: add SmcKeyType enum; add SmcSealKey enum; add SmcGenerateAesKekOption struct; add smcPrepareGenerateAesKekOption(). --- include/core/aes.h | 5 + include/core/key_sources.h | 172 ++++++++ include/core/keys.h | 9 - include/core/nxdt_utils.h | 5 +- include/core/sha3.h | 2 +- include/core/smc.h | 81 ++++ source/core/aes.c | 15 + source/core/fs_ext.c | 8 +- source/core/keys.c | 853 ++++++++++++++----------------------- source/core/nca.c | 174 +++++--- source/core/nxdt_utils.c | 53 ++- source/core/services.c | 17 +- source/core/sha3.c | 2 +- source/core/tik.c | 4 +- source/main.cpp | 2 +- source/root_view.cpp | 2 +- 16 files changed, 744 insertions(+), 660 deletions(-) create mode 100644 include/core/key_sources.h create mode 100644 include/core/smc.h diff --git a/include/core/aes.h b/include/core/aes.h index f45d0f2..663fbab 100644 --- a/include/core/aes.h +++ b/include/core/aes.h @@ -28,6 +28,11 @@ extern "C" { #endif +/// One-shot function to perform AES-128-ECB crypto. +/// 'dst', 'src' and 'key' must all have a size of at least AES_BLOCK_SIZE bytes. +/// 'dst' and 'src' can both point to the same address. +void aes128EcbCrypt(void *dst, const void *src, const void *key, bool encrypt); + /// Performs an AES-128-XTS crypto operation using the non-standard Nintendo XTS tweak. /// The Aes128XtsContext element should have been previously initialized with aes128XtsContextCreate(). 'encrypt' should match the value of 'is_encryptor' used with that call. /// 'dst' and 'src' can both point to the same address. diff --git a/include/core/key_sources.h b/include/core/key_sources.h new file mode 100644 index 0000000..43f6948 --- /dev/null +++ b/include/core/key_sources.h @@ -0,0 +1,172 @@ +/* + * key_sources.h + * + * Copyright (c) 2019-2023, shchmue. + * Copyright (c) 2020-2023, DarkMatterCore . + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * + * nxdumptool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nxdumptool is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Last updated on: 2023-04-08. */ +/* Current key generation: NcaKeyGeneration_Since1600NUP (16 / 0F). */ + +#pragma once + +#ifndef __KEY_SOURCES_H__ +#define __KEY_SOURCES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* TODO: update on TSEC root key changes. */ +#define TSEC_ROOT_KEY_VERSION 2 + +/* Used to derive all previous master keys using the latest master key on retail units. */ +/* TODO: update on master key changes. */ +static const u8 g_masterKeyVectorsProd[NcaKeyGeneration_Current][AES_128_KEY_SIZE] = { + { 0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D }, ///< Zeroes encrypted with master key 00. + { 0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD }, ///< Master key 00 encrypted with master key 01. + { 0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72 }, ///< Master key 01 encrypted with master key 02. + { 0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07 }, ///< Master key 02 encrypted with master key 03. + { 0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9 }, ///< Master key 03 encrypted with master key 04. + { 0xEB, 0xF5, 0x6F, 0x83, 0x61, 0x9E, 0xF8, 0xFA, 0xE0, 0x87, 0xD7, 0xA1, 0x4E, 0x25, 0x36, 0xEE }, ///< Master key 04 encrypted with master key 05. + { 0x1E, 0x1E, 0x22, 0xC0, 0x5A, 0x33, 0x3C, 0xB9, 0x0B, 0xA9, 0x03, 0x04, 0xBA, 0xDB, 0x07, 0x57 }, ///< Master key 05 encrypted with master key 06. + { 0xA4, 0xD4, 0x52, 0x6F, 0xD1, 0xE4, 0x36, 0xAA, 0x9F, 0xCB, 0x61, 0x27, 0x1C, 0x67, 0x65, 0x1F }, ///< Master key 06 encrypted with master key 07. + { 0xEA, 0x60, 0xB3, 0xEA, 0xCE, 0x8F, 0x24, 0x46, 0x7D, 0x33, 0x9C, 0xD1, 0xBC, 0x24, 0x98, 0x29 }, ///< Master key 07 encrypted with master key 08. + { 0x4D, 0xD9, 0x98, 0x42, 0x45, 0x0D, 0xB1, 0x3C, 0x52, 0x0C, 0x9A, 0x44, 0xBB, 0xAD, 0xAF, 0x80 }, ///< Master key 08 encrypted with master key 09. + { 0xB8, 0x96, 0x9E, 0x4A, 0x00, 0x0D, 0xD6, 0x28, 0xB3, 0xD1, 0xDB, 0x68, 0x5F, 0xFB, 0xE1, 0x2A }, ///< Master key 09 encrypted with master key 0A. + { 0xC1, 0x8D, 0x16, 0xBB, 0x2A, 0xE4, 0x1D, 0xD4, 0xC2, 0xC1, 0xB6, 0x40, 0x94, 0x35, 0x63, 0x98 }, ///< Master key 0A encrypted with master key 0B. + { 0xA3, 0x24, 0x65, 0x75, 0xEA, 0xCC, 0x6E, 0x8D, 0xFB, 0x5A, 0x16, 0x50, 0x74, 0xD2, 0x15, 0x06 }, ///< Master key 0B encrypted with master key 0C. + { 0x83, 0x67, 0xAF, 0x01, 0xCF, 0x93, 0xA1, 0xAB, 0x80, 0x45, 0xF7, 0x3F, 0x72, 0xFD, 0x3B, 0x38 }, ///< Master key 0C encrypted with master key 0D. + { 0xB1, 0x81, 0xA6, 0x0D, 0x72, 0xC7, 0xEE, 0x15, 0x21, 0xF3, 0xC0, 0xB5, 0x6B, 0x61, 0x6D, 0xE7 }, ///< Master key 0D encrypted with master key 0E. + { 0xAF, 0x11, 0x4C, 0x67, 0x17, 0x7A, 0x52, 0x43, 0xF7, 0x70, 0x2F, 0xC7, 0xEF, 0x81, 0x72, 0x16 }, ///< Master key 0E encrypted with master key 0F. +}; + +/* Used to derive all previous master keys using the latest master key on development units. */ +/* TODO: update on master key changes. */ +static const u8 g_masterKeyVectorsDev[NcaKeyGeneration_Current][AES_128_KEY_SIZE] = { + { 0x46, 0x22, 0xB4, 0x51, 0x9A, 0x7E, 0xA7, 0x7F, 0x62, 0xA1, 0x1F, 0x8F, 0xC5, 0x3A, 0xDB, 0xFE }, ///< Zeroes encrypted with master key 00. + { 0x39, 0x33, 0xF9, 0x31, 0xBA, 0xE4, 0xA7, 0x21, 0x2C, 0xDD, 0xB7, 0xD8, 0xB4, 0x4E, 0x37, 0x23 }, ///< Master key 00 encrypted with master key 01. + { 0x97, 0x29, 0xB0, 0x32, 0x43, 0x14, 0x8C, 0xA6, 0x85, 0xE9, 0x5A, 0x94, 0x99, 0x39, 0xAC, 0x5D }, ///< Master key 01 encrypted with master key 02. + { 0x2C, 0xCA, 0x9C, 0x31, 0x1E, 0x07, 0xB0, 0x02, 0x97, 0x0A, 0xD8, 0x03, 0xA2, 0x76, 0x3F, 0xA3 }, ///< Master key 02 encrypted with master key 03. + { 0x9B, 0x84, 0x76, 0x14, 0x72, 0x94, 0x52, 0xCB, 0x54, 0x92, 0x9B, 0xC4, 0x8C, 0x5B, 0x0F, 0xBA }, ///< Master key 03 encrypted with master key 04. + { 0x78, 0xD5, 0xF1, 0x20, 0x3D, 0x16, 0xE9, 0x30, 0x32, 0x27, 0x34, 0x6F, 0xCF, 0xE0, 0x27, 0xDC }, ///< Master key 04 encrypted with master key 05. + { 0x6F, 0xD2, 0x84, 0x1D, 0x05, 0xEC, 0x40, 0x94, 0x5F, 0x18, 0xB3, 0x81, 0x09, 0x98, 0x8D, 0x4E }, ///< Master key 05 encrypted with master key 06. + { 0x37, 0xAF, 0xAB, 0x35, 0x79, 0x09, 0xD9, 0x48, 0x29, 0xD2, 0xDB, 0xA5, 0xA5, 0xF5, 0x30, 0x19 }, ///< Master key 06 encrypted with master key 07. + { 0xEC, 0xE1, 0x46, 0x89, 0x37, 0xFD, 0xD2, 0x15, 0x8C, 0x3F, 0x24, 0x82, 0xEF, 0x49, 0x68, 0x04 }, ///< Master key 07 encrypted with master key 08. + { 0x43, 0x3D, 0xC5, 0x3B, 0xEF, 0x91, 0x02, 0x21, 0x61, 0x54, 0x63, 0x8A, 0x35, 0xE7, 0xCA, 0xEE }, ///< Master key 08 encrypted with master key 09. + { 0x6C, 0x2E, 0xCD, 0xB3, 0x34, 0x61, 0x77, 0xF5, 0xF9, 0xB1, 0xDD, 0x61, 0x98, 0x19, 0x3E, 0xD4 }, ///< Master key 09 encrypted with master key 0A. + { 0x21, 0x88, 0x6B, 0x10, 0x9E, 0x83, 0xD6, 0x52, 0xAB, 0x08, 0xDB, 0x6D, 0x39, 0xFF, 0x1C, 0x9C }, ///< Master key 0A encrypted with master key 0B. + { 0x8A, 0xCE, 0xC4, 0x7F, 0xBE, 0x08, 0x61, 0x88, 0xD3, 0x73, 0x64, 0x51, 0xE2, 0xB6, 0x53, 0x15 }, ///< Master key 0B encrypted with master key 0C. + { 0x08, 0xE0, 0xF4, 0xBE, 0xAA, 0x6E, 0x5A, 0xC3, 0xA6, 0xBC, 0xFE, 0xB9, 0xE2, 0xA3, 0x24, 0x12 }, ///< Master key 0C encrypted with master key 0D. + { 0xD6, 0x80, 0x98, 0xC0, 0xFA, 0xC7, 0x13, 0xCB, 0x93, 0xD2, 0x0B, 0x82, 0x4C, 0xA1, 0x7B, 0x8D }, ///< Master key 0D encrypted with master key 0E. + { 0x78, 0x66, 0x19, 0xBD, 0x86, 0xE7, 0xC1, 0x09, 0x9B, 0x6F, 0x92, 0xB2, 0x58, 0x7D, 0xCF, 0x26 }, ///< Master key 0E encrypted with master key 0F. +}; + +/* Used to derive a master KEK using the TSEC root key on Erista units. */ +/* TODO: update on master key changes. */ +static const u8 g_eristaMasterKekSource[AES_128_KEY_SIZE] = { + 0x99, 0x22, 0x09, 0x57, 0xA7, 0xF9, 0x5E, 0x94, 0xFE, 0x78, 0x7F, 0x41, 0xD6, 0xE7, 0x56, 0xE6 +}; + +/* Used to derive a master KEK on retail Mariko units. */ +/* TODO: update on master key changes. */ +static const u8 g_marikoMasterKekSourceProd[AES_128_KEY_SIZE] = { + 0xA5, 0xEC, 0x16, 0x39, 0x1A, 0x30, 0x16, 0x08, 0x2E, 0xCF, 0x09, 0x6F, 0x5E, 0x7C, 0xEE, 0xA9 +}; + +/* Used to derive a master KEK on development Mariko units. */ +/* TODO: update on master key changes. */ +static const u8 g_marikoMasterKekSourceDev[AES_128_KEY_SIZE] = { + 0x3A, 0x9C, 0xF0, 0x39, 0x70, 0x23, 0xF6, 0xAF, 0x71, 0x44, 0x60, 0xF4, 0x6D, 0xED, 0xA1, 0xD6 +}; + +/* Used to derive master keys from master KEKs. Found in TrustZone / Secure Monitor. */ +static const u8 g_masterKeySource[AES_128_KEY_SIZE] = { + 0xD8, 0xA2, 0x41, 0x0A, 0xC6, 0xC5, 0x90, 0x01, 0xC6, 0x1D, 0x6A, 0x26, 0x7C, 0x51, 0x3F, 0x3C +}; + +/* Randomly generated KEK source used to derive official CardInfo area keys. */ +static const u8 g_gcCardInfoKekSource[AES_128_KEY_SIZE] = { + 0xDE, 0xC6, 0x3F, 0x6A, 0xBF, 0x37, 0x72, 0x0B, 0x7E, 0x54, 0x67, 0x6A, 0x2D, 0xEF, 0xDD, 0x97 +}; + +/* CardInfo area key used in retail units. Obfuscated using g_gcCardInfoKekSource and SMC AES engine keydata. */ +/* Hardcoded because it can only be retrieved in plaintext form from FS program memory under HOS 9.0.0+ -- and we wish to use it under previous HOS versions as well. */ +static const u8 g_gcCardInfoKeySourceProd[AES_128_KEY_SIZE] = { + 0xF4, 0x92, 0x06, 0x52, 0xD6, 0x37, 0x70, 0xAF, 0xB1, 0x9C, 0x6F, 0x63, 0x09, 0x01, 0xF6, 0x29 +}; + +/* CardInfo area key used in development units. Obfuscated using g_gcCardInfoKekSource and SMC AES engine keydata. */ +/* Hardcoded because it can only be retrieved in plaintext form from FS program memory under HOS 9.0.0+ -- and we wish to use it under previous HOS versions as well. */ +static const u8 g_gcCardInfoKeySourceDev[AES_128_KEY_SIZE] = { + 0x54, 0xC3, 0xE1, 0xF2, 0x5B, 0x3A, 0x5E, 0xC0, 0x4C, 0xA7, 0xCF, 0xFB, 0xE1, 0xAE, 0x16, 0xCA +}; + +/* KEK source used to generate ticket common keys, which in turn are used to decrypt titlekeys from tickets. Also known as "titlekek_source". */ +/* Found in TrustZone / Secure Monitor. */ +static const u8 g_ticketCommonKeySource[AES_128_KEY_SIZE] = { + 0x1E, 0xDC, 0x7B, 0x3B, 0x60, 0xE6, 0xB4, 0xD8, 0x78, 0xB8, 0x17, 0x15, 0x98, 0x5E, 0x62, 0x9B +}; + +/* Used by GenerateAesKek to derive keys. Found in TrustZone / Secure Monitor. */ +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 }, +}; + +/* Used by GenerateAesKek to derive keys. Found in TrustZone / Secure Monitor. */ +static const u8 g_smcSealKeyMasks[SmcSealKey_Count][AES_128_KEY_SIZE] = { + [SmcSealKey_LoadAesKey] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + [SmcSealKey_DecryptDeviceUniqueData] = { 0xA2, 0xAB, 0xBF, 0x9C, 0x92, 0x2F, 0xBB, 0xE3, 0x78, 0x79, 0x9B, 0xC0, 0xCC, 0xEA, 0xA5, 0x74 }, + [SmcSealKey_ImportLotusKey] = { 0x57, 0xE2, 0xD9, 0x45, 0xE4, 0x92, 0xF4, 0xFD, 0xC3, 0xF9, 0x86, 0x38, 0x89, 0x78, 0x9F, 0x3C }, + [SmcSealKey_ImportEsDeviceKey] = { 0xE5, 0x4D, 0x9A, 0x02, 0xF0, 0x4F, 0x5F, 0xA8, 0xAD, 0x76, 0x0A, 0xF6, 0x32, 0x95, 0x59, 0xBB }, + [SmcSealKey_ReencryptDeviceUniqueData] = { 0x59, 0xD9, 0x31, 0xF4, 0xA7, 0x97, 0xB8, 0x14, 0x40, 0xD6, 0xA2, 0x60, 0x2B, 0xED, 0x15, 0x31 }, + [SmcSealKey_ImportSslKey] = { 0xFD, 0x6A, 0x25, 0xE5, 0xD8, 0x38, 0x7F, 0x91, 0x49, 0xDA, 0xF8, 0x59, 0xA8, 0x28, 0xE6, 0x75 }, + [SmcSealKey_ImportEsClientCertKey] = { 0x89, 0x96, 0x43, 0x9A, 0x7C, 0xD5, 0x59, 0x55, 0x24, 0xD5, 0x24, 0x18, 0xAB, 0x6C, 0x04, 0x61 } +}; + +/* Used by GenerateAesKey. Found in SPL. */ +static const u8 g_aesKeyGenerationSource[AES_128_KEY_SIZE] = { + 0x89, 0x61, 0x5E, 0xE0, 0x5C, 0x31, 0xB6, 0x80, 0x5F, 0xE5, 0x8F, 0x3D, 0xA2, 0x4F, 0x7A, 0xA8 +}; + +/* Used to derive the NCA header key. Found in the .rodata segment from the FS sysmodule. */ +static const u8 g_ncaHeaderKekSource[AES_128_KEY_SIZE] = { + 0x1F, 0x12, 0x91, 0x3A, 0x4A, 0xCB, 0xF0, 0x0D, 0x4C, 0xDE, 0x3A, 0xF6, 0xD5, 0x23, 0x88, 0x2A +}; + +/* Used to derive the NCA header key. Found in the .data segment from the FS sysmodule. */ +static const u8 g_ncaHeaderKeySource[AES_128_KEY_SIZE * 2] = { + 0x5A, 0x3E, 0xD8, 0x4F, 0xDE, 0xC0, 0xD8, 0x26, 0x31, 0xF7, 0xE2, 0x5D, 0x19, 0x7B, 0xF5, 0xD0, + 0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2 +}; + +/* Key sources used to derive NCA key area encryption keys required to handle key areas from NCA headers. Found in the .rodata segment from the FS sysmodule. */ +static const u8 g_ncaKeyAreaEncryptionKeySources[NcaKeyAreaEncryptionKeyIndex_Count][AES_128_KEY_SIZE] = { + { 0x7F, 0x59, 0x97, 0x1E, 0x62, 0x9F, 0x36, 0xA1, 0x30, 0x98, 0x06, 0x6F, 0x21, 0x44, 0xC3, 0x0D }, ///< Application. + { 0x32, 0x7D, 0x36, 0x08, 0x5A, 0xD1, 0x75, 0x8D, 0xAB, 0x4E, 0x6F, 0xBA, 0xA5, 0x55, 0xD8, 0x82 }, ///< Ocean. + { 0x87, 0x45, 0xF1, 0xBB, 0xA6, 0xBE, 0x79, 0x64, 0x7D, 0x04, 0x8B, 0xA6, 0x7B, 0x5F, 0xDA, 0x4A } ///< System. +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __KEY_SOURCES_H__ */ diff --git a/include/core/keys.h b/include/core/keys.h index 331bb6d..ab77ae7 100644 --- a/include/core/keys.h +++ b/include/core/keys.h @@ -37,16 +37,7 @@ bool keysLoadKeyset(void); /// Returns a pointer to the AES-128-XTS NCA header key, or NULL if keydata hasn't been loaded. const u8 *keysGetNcaHeaderKey(void); -/// Returns a pointer to the RSA-2048-PSS modulus for the NCA header main signature, using the provided key generation value. -const u8 *keysGetNcaMainSignatureModulus(u8 key_generation); - -/// Decrypts 'src' into 'dst' using the provided key area encryption key index and key generation values. Runtime sealed keydata from the SMC AES engine is used to achieve this. -/// Both 'dst' and 'src' buffers must have a size of at least AES_128_KEY_SIZE. -/// Returns false if an error occurs or if keydata hasn't been loaded. -bool keysDecryptNcaKeyAreaEntry(u8 kaek_index, u8 key_generation, void *dst, const void *src); - /// Returns a pointer to an AES-128-ECB NCA key area encryption key using the provided key area encryption key index and key generation values, or NULL if keydata hasn't been loaded. -/// This data is loaded from the Lockpick_RCM keys file. const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation); /// Decrypts a RSA-OAEP wrapped titlekey using console-specific keydata. diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index 37ff75a..21f728e 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -87,11 +87,14 @@ bool utilsCommitSdCardFileSystemChanges(void); /// Returns a UtilsCustomFirmwareType value. u8 utilsGetCustomFirmwareType(void); +/// Returns true if the application is running under a Mariko unit. +bool utilsIsMarikoUnit(void); + /// Returns true if the application is running under a development unit. bool utilsIsDevelopmentUnit(void); /// Returns true if the application is running under applet mode. -bool utilsAppletModeCheck(void); +bool utilsIsAppletMode(void); /// Returns a pointer to the FsStorage object for the eMMC BIS System partition. FsStorage *utilsGetEmmcBisSystemPartitionStorage(void); diff --git a/include/core/sha3.h b/include/core/sha3.h index fd3f9ff..2d98552 100644 --- a/include/core/sha3.h +++ b/include/core/sha3.h @@ -1,7 +1,7 @@ /* * sha3.h * - * Copyright (c) Atmosphère-NX + * Copyright (c) Atmosphère-NX. * Copyright (c) 2022, DarkMatterCore . * * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). diff --git a/include/core/smc.h b/include/core/smc.h new file mode 100644 index 0000000..061825d --- /dev/null +++ b/include/core/smc.h @@ -0,0 +1,81 @@ +/* + * smc.h + * + * Copyright (c) Atmosphère-NX. + * Copyright (c) 2020-2023, DarkMatterCore . + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * + * nxdumptool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nxdumptool is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#ifndef __SMC_H__ +#define __SMC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SmcKeyType_Default = 0, ///< Also known as "aes_kek_generation_source". + SmcKeyType_NormalOnly = 1, + SmcKeyType_RecoveryOnly = 2, + SmcKeyType_NormalAndRecovery = 3, + SmcKeyType_Count = 4 +} SmcKeyType; + +typedef enum { + SmcSealKey_LoadAesKey = 0, + SmcSealKey_DecryptDeviceUniqueData = 1, + SmcSealKey_ImportLotusKey = 2, + SmcSealKey_ImportEsDeviceKey = 3, + SmcSealKey_ReencryptDeviceUniqueData = 4, + SmcSealKey_ImportSslKey = 5, + SmcSealKey_ImportEsClientCertKey = 6, + SmcSealKey_Count = 7 +} SmcSealKey; + +typedef struct { + union { + u32 value; ///< Can be used with spl calls. + struct { + u32 is_device_unique : 1; + u32 key_type_idx : 4; ///< SmcKeyType. + u32 seal_key_idx : 3; ///< SmcSealKey. + u32 reserved : 24; + } fields; + }; +} SmcGenerateAesKekOption; + +/// Helper inline functions. + +NX_INLINE bool smcPrepareGenerateAesKekOption(bool is_device_unique, u32 key_type_idx, u32 seal_key_idx, SmcGenerateAesKekOption *out) +{ + if (key_type_idx >= SmcKeyType_Count || seal_key_idx >= SmcSealKey_Count) return false; + + out->fields.is_device_unique = (u32)(is_device_unique & 1); + out->fields.key_type_idx = key_type_idx; + out->fields.seal_key_idx = seal_key_idx; + out->fields.reserved = 0; + + return true; +} + +#ifdef __cplusplus +} +#endif + +#endif /* __SMC_H__ */ diff --git a/source/core/aes.c b/source/core/aes.c index d991672..8fe2649 100644 --- a/source/core/aes.c +++ b/source/core/aes.c @@ -21,6 +21,21 @@ #include "nxdt_utils.h" +void aes128EcbCrypt(void *dst, const void *src, const void *key, bool encrypt) +{ + if (!dst || !src || !key) return; + + Aes128Context ctx = {0}; + aes128ContextCreate(&ctx, key, encrypt); + + if (encrypt) + { + aes128EncryptBlock(&ctx, dst, src); + } else { + aes128DecryptBlock(&ctx, dst, src); + } +} + size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt) { if (!ctx || !dst || !src || !size || !sector_size || (size % sector_size) != 0) diff --git a/source/core/fs_ext.c b/source/core/fs_ext.c index c5b7b77..e072c0b 100644 --- a/source/core/fs_ext.c +++ b/source/core/fs_ext.c @@ -25,7 +25,7 @@ /* IFileSystemProxy. */ Result fsOpenGameCardStorage(FsStorage *out, const FsGameCardHandle *handle, u32 partition) { - struct { + const struct { FsGameCardHandle handle; u32 partition; } in = { *handle, partition }; @@ -47,7 +47,7 @@ Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out) /* IDeviceOperator. */ Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id) { - struct { + const struct { FsGameCardHandle handle; } in = { *handle }; @@ -66,7 +66,7 @@ Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCard Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const FsGameCardHandle *handle, FsGameCardCertificate *out) { - struct { + const struct { FsGameCardHandle handle; u64 buf_size; } in = { *handle, sizeof(FsGameCardCertificate) }; @@ -81,7 +81,7 @@ Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const F Result fsDeviceOperatorGetGameCardIdSet(FsDeviceOperator *d, FsGameCardIdSet *out) { - struct { + const struct { u64 buf_size; } in = { sizeof(FsGameCardIdSet) }; diff --git a/source/core/keys.c b/source/core/keys.c index 06e124f..7264336 100644 --- a/source/core/keys.c +++ b/source/core/keys.c @@ -23,54 +23,50 @@ #include "nxdt_utils.h" #include "keys.h" -#include "mem.h" #include "nca.h" #include "rsa.h" +#include "aes.h" +#include "smc.h" +#include "key_sources.h" #define ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT 0x10001 /* Type definitions. */ -typedef bool (*KeysIsKeyMandatoryFunction)(void); /* Used to determine if a key is mandatory or not at runtime. */ - typedef struct { - char name[64]; - u8 hash[SHA256_HASH_SIZE]; - u64 size; - void *dst; - KeysIsKeyMandatoryFunction mandatory_func; ///< If NULL, key is mandatory. -} KeysMemoryKey; + ///< 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. + u8 tsec_root_key[AES_128_KEY_SIZE]; -typedef struct { - MemoryLocation location; - u32 key_count; - KeysMemoryKey keys[]; -} KeysMemoryInfo; + ///< 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. + 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. + u8 master_keys[NcaKeyGeneration_Max][AES_128_KEY_SIZE]; -typedef struct { ///< AES-128-XTS key needed to handle NCA header crypto. - u8 nca_header_kek_source[AES_128_KEY_SIZE]; ///< Retrieved from the .rodata segment in the FS sysmodule. - u8 nca_header_key_source[AES_128_KEY_SIZE * 2]; ///< Retrieved from the .data segment in the FS sysmodule. - u8 nca_header_key[AES_128_KEY_SIZE * 2]; ///< Generated from nca_header_kek (sealed by the SMC AES engine) and nca_header_key_source. - - ///< RSA-2048-PSS moduli used to verify the main signature from NCA headers. - u8 nca_main_signature_moduli_prod[NcaSignatureKeyGeneration_Max][RSA2048_PUBKEY_SIZE]; ///< Moduli used in retail units. Retrieved from the .rodata segment in the FS sysmodule. - u8 nca_main_signature_moduli_dev[NcaSignatureKeyGeneration_Max][RSA2048_PUBKEY_SIZE]; ///< Moduli used in development units. Retrieved from the .rodata segment in the FS sysmodule. + ///< Generated from hardcoded key sources. + u8 nca_header_key[AES_128_KEY_SIZE * 2]; ///< AES-128-ECB keys needed to handle key area crypto from NCA headers. - u8 nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Count][AES_128_KEY_SIZE]; ///< Retrieved from the .rodata segment in the FS sysmodule. - u8 nca_kaek_sealed[NcaKeyAreaEncryptionKeyIndex_Count][NcaKeyGeneration_Max][AES_128_KEY_SIZE]; ///< Generated from nca_kaek_sources. Sealed by the SMC AES engine. - u8 nca_kaek[NcaKeyAreaEncryptionKeyIndex_Count][NcaKeyGeneration_Max][AES_128_KEY_SIZE]; ///< Unsealed key area encryption keys. Retrieved from the Lockpick_RCM keys file. - ///< Verified using a hardcoded hash. + ///< Generated from hardcoded key sources and master keys. + 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. - ///< Verified by decrypting the eTicket RSA device key. - u8 eticket_rsa_kek[AES_128_KEY_SIZE]; ///< eTicket RSA key encryption key (generic). Retrieved from the Lockpick_RCM keys file. - u8 eticket_rsa_kek_personalized[AES_128_KEY_SIZE]; ///< eTicket RSA key encryption key (console-specific). Retrieved from the Lockpick_RCM keys file. + ///< Retrieved from the Lockpick_RCM 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]; ///< AES-128-ECB keys needed to decrypt titlekeys. - u8 ticket_common_keys[NcaKeyGeneration_Max][AES_128_KEY_SIZE]; ///< Retrieved from the Lockpick_RCM keys file. Verified using a hardcoded hash. -} KeysNcaKeyset; + ///< Generated from a hardcoded key source and master keys. + u8 ticket_common_keys[NcaKeyGeneration_Max][AES_128_KEY_SIZE]; + + ///< AES-128-CBC key needed to decrypt the CardInfo area from gamecard headers. + ///< Generated from hardcoded key sources. + u8 gc_cardinfo_key[AES_128_KEY_SIZE]; +} KeysNxKeyset; /// Used to parse the eTicket RSA device key retrieved from PRODINFO via setcalGetEticketDeviceKey(). /// Everything after the AES CTR is encrypted using the eTicket RSA device key encryption key. @@ -86,226 +82,39 @@ typedef struct { NXDT_ASSERT(EticketRsaDeviceKey, 0x240); -/// AES-128-CBC keys needed to decrypt the CardInfo area from gamecard headers. -typedef struct { - const u8 gc_cardinfo_kek_source[AES_128_KEY_SIZE]; ///< Randomly generated KEK source to decrypt official CardInfo area keys. - const u8 gc_cardinfo_key_prod_source[AES_128_KEY_SIZE]; ///< CardInfo area key used in retail units. Obfuscated using the above KEK source and SMC AES engine keydata. - const u8 gc_cardinfo_key_dev_source[AES_128_KEY_SIZE]; ///< CardInfo area key used in development units. Obfuscated using the above KEK source and SMC AES engine keydata. - u8 gc_cardinfo_key_prod[AES_128_KEY_SIZE]; ///< Generated from gc_cardinfo_kek (sealed by the SMC AES engine) and gc_cardinfo_key_prod_source. - u8 gc_cardinfo_key_dev[AES_128_KEY_SIZE]; ///< Generated from gc_cardinfo_kek (sealed by the SMC AES engine) and gc_cardinfo_key_dev_source. -} KeysGameCardKeyset; - /* Function prototypes. */ -static bool keysIsProductionModulus1xMandatory(void); -static bool keysIsProductionModulus9xMandatory(void); - -static bool keysIsDevelopmentModulus1xMandatory(void); -static bool keysIsDevelopmentModulus9xMandatory(void); - -static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info); - -static bool keysDeriveNcaHeaderKey(void); -static bool keysDeriveSealedNcaKeyAreaEncryptionKeys(void); +static bool keysIsKeyEmpty(const void *key); static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **value); static char keysConvertHexDigitToBinary(char c); static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size); static bool keysReadKeysFromFile(void); -static bool keysVerifyLockpickRcmData(void); +static bool keysDeriveMasterKeys(void); +static bool keysDeriveNcaHeaderKey(void); +static bool keysDerivePerGenerationKeys(void); +static bool keysDeriveGcCardInfoKey(void); static bool keysGetDecryptedEticketRsaDeviceKey(void); static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void *n); -static bool keysDeriveGameCardKeys(void); +static bool keysGenerateAesKek(const u8 *kek_src, u8 key_generation, SmcGenerateAesKekOption option, u8 *out_kek); +static bool keysLoadAesKey(const u8 *kek, const u8 *key_src, u8 *out_key); +static bool keysGenerateAesKey(const u8 *kek, const u8 *key_src, u8 *out_key); -static bool keysGenerateAesKey(const u8 *kek_source, const u8 *key_source, u32 key_generation, u32 option, u8 *out_key); +static bool keysLoadAesKeyFromAesKek(const u8 *kek_src, u8 key_generation, SmcGenerateAesKekOption option, const u8 *key_src, u8 *out_key); +static bool keysGenerateAesKeyFromAesKek(const u8 *kek_src, u8 key_generation, SmcGenerateAesKekOption option, const u8 *key_src, u8 *out_key); /* Global variables. */ static bool g_keysetLoaded = false; static Mutex g_keysetMutex = 0; -static KeysNcaKeyset g_ncaKeyset = {0}; - -/// TODO: update on master key changes. -static const u8 g_ncaKaekBlockHashes[2][NcaKeyAreaEncryptionKeyIndex_Count][SHA256_HASH_SIZE] = { - /* Production. */ - { - /* Application. */ - { - 0xAE, 0x82, 0xD8, 0xE5, 0x1A, 0xC3, 0x5F, 0xC0, 0xBF, 0xE1, 0xC0, 0x88, 0x69, 0xB9, 0x69, 0xCE, - 0x56, 0xD4, 0x99, 0xE6, 0x97, 0x80, 0xFE, 0x1C, 0x3D, 0xB7, 0xEA, 0x9C, 0xD8, 0xD7, 0xF2, 0x12 - }, - /* Ocean. */ - { - 0x6F, 0xE0, 0x38, 0xC2, 0xAF, 0xB8, 0xF7, 0xDC, 0xC4, 0x97, 0x0A, 0x19, 0xCC, 0xE7, 0xD3, 0x10, - 0x03, 0x70, 0x2C, 0xF5, 0x51, 0xF1, 0x01, 0xDE, 0x88, 0x4E, 0x47, 0xD3, 0x8D, 0xC2, 0xFD, 0x8A - }, - /* System. */ - { - 0x2F, 0xEB, 0xA2, 0x09, 0x42, 0x51, 0xB5, 0x88, 0x3D, 0x52, 0x3E, 0xE6, 0x47, 0x7F, 0xDD, 0xFD, - 0x3F, 0xB2, 0x7B, 0xED, 0xBA, 0x8C, 0x98, 0x34, 0xA2, 0xF9, 0xA9, 0x5A, 0x81, 0x1A, 0x7E, 0xA9 - } - }, - /* Development. */ - { - /* Application. */ - { - 0xD5, 0x28, 0x5F, 0xDB, 0x38, 0x6D, 0x0E, 0x3C, 0xA1, 0x14, 0x6F, 0x4D, 0x32, 0xA6, 0x22, 0x23, - 0x8D, 0xD7, 0x81, 0xAF, 0x68, 0x71, 0x76, 0x06, 0x8B, 0x71, 0xC3, 0x87, 0x83, 0x4B, 0x86, 0xC8 - }, - /* Ocean. */ - { - 0x76, 0xD4, 0xD7, 0x1C, 0xAA, 0x19, 0x97, 0x5B, 0x74, 0xAE, 0xFF, 0x2D, 0xEA, 0x27, 0x1B, 0xC6, - 0xED, 0xF7, 0xB5, 0xD0, 0xA3, 0xFF, 0xE7, 0xEA, 0x1A, 0x99, 0xB3, 0x8C, 0xDD, 0x4F, 0xAB, 0x5C - }, - /* System. */ - { - 0x46, 0x5E, 0xB1, 0x43, 0x37, 0x83, 0x52, 0x84, 0x73, 0x08, 0xCA, 0x9D, 0xDE, 0x64, 0x8C, 0x76, - 0x58, 0xB3, 0x9A, 0x42, 0xF1, 0xC5, 0xA9, 0x60, 0xA6, 0xED, 0xF3, 0xB8, 0xAA, 0x44, 0xEF, 0x41 - } - } -}; - -/// TODO: update on master key changes. -static const u8 g_ticketCommonKeysBlockHashes[2][SHA256_HASH_SIZE] = { - /* Production. */ - { - 0x6F, 0x42, 0x8A, 0x53, 0x51, 0x61, 0xC7, 0x69, 0x90, 0x21, 0xC5, 0x71, 0xC6, 0x89, 0x2B, 0x33, - 0xBB, 0x1D, 0xF6, 0xA8, 0x26, 0x12, 0x21, 0x7D, 0x81, 0x9B, 0xCC, 0x78, 0x3A, 0x2D, 0xD7, 0x6C - }, - /* Development. */ - { - 0x88, 0x61, 0x4D, 0x1E, 0xC3, 0xF0, 0x51, 0x94, 0xB7, 0x35, 0xAA, 0x2E, 0xFC, 0x4D, 0x7A, 0x7C, - 0xFB, 0x25, 0x8A, 0x0C, 0x60, 0x68, 0x89, 0x04, 0x68, 0xAB, 0x21, 0xA0, 0x34, 0x29, 0x02, 0xE9 - } -}; - static SetCalRsa2048DeviceKey g_eTicketRsaDeviceKey = {0}; +static KeysNxKeyset g_nxKeyset = {0}; -static KeysMemoryInfo g_fsRodataMemoryInfo = { - .location = { - .program_id = FS_SYSMODULE_TID, - .mask = MemoryProgramSegmentType_Rodata, - .data = NULL, - .data_size = 0 - }, - .key_count = 8, - .keys = { - { - .name = "nca_header_kek_source", - .hash = { - 0x18, 0x88, 0xCA, 0xED, 0x55, 0x51, 0xB3, 0xED, 0xE0, 0x14, 0x99, 0xE8, 0x7C, 0xE0, 0xD8, 0x68, - 0x27, 0xF8, 0x08, 0x20, 0xEF, 0xB2, 0x75, 0x92, 0x10, 0x55, 0xAA, 0x4E, 0x2A, 0xBD, 0xFF, 0xC2 - }, - .size = sizeof(g_ncaKeyset.nca_header_kek_source), - .dst = g_ncaKeyset.nca_header_kek_source, - .mandatory_func = NULL - }, - { - .name = "nca_main_signature_modulus_prod_00", - .hash = { - 0xF9, 0x2E, 0x84, 0x98, 0x17, 0x2C, 0xAF, 0x9C, 0x20, 0xE3, 0xF1, 0xF7, 0xD3, 0xE7, 0x2C, 0x62, - 0x50, 0xA9, 0x40, 0x7A, 0xE7, 0x84, 0xE0, 0x03, 0x58, 0x07, 0x85, 0xA5, 0x68, 0x0B, 0x80, 0x33 - }, - .size = sizeof(g_ncaKeyset.nca_main_signature_moduli_prod[NcaSignatureKeyGeneration_Since100NUP]), - .dst = g_ncaKeyset.nca_main_signature_moduli_prod[NcaSignatureKeyGeneration_Since100NUP], - .mandatory_func = &keysIsProductionModulus1xMandatory - }, - { - .name = "nca_main_signature_modulus_prod_01", - .hash = { - 0x5F, 0x6B, 0xE3, 0x1C, 0x31, 0x6E, 0x7C, 0xB2, 0x1C, 0xA7, 0xB9, 0xA1, 0x70, 0x6A, 0x9D, 0x58, - 0x04, 0xEB, 0x90, 0x53, 0x72, 0xEF, 0xCB, 0x56, 0xD1, 0x93, 0xF2, 0xAF, 0x9E, 0x8A, 0xD1, 0xFA - }, - .size = sizeof(g_ncaKeyset.nca_main_signature_moduli_prod[NcaSignatureKeyGeneration_Since900NUP]), - .dst = g_ncaKeyset.nca_main_signature_moduli_prod[NcaSignatureKeyGeneration_Since900NUP], - .mandatory_func = &keysIsProductionModulus9xMandatory - }, - { - .name = "nca_main_signature_modulus_dev_00", - .hash = { - 0x50, 0xF8, 0x26, 0xBB, 0x13, 0xFE, 0xB2, 0x6D, 0x83, 0xCF, 0xFF, 0xD8, 0x38, 0x45, 0xC3, 0x51, - 0x4D, 0xCB, 0x06, 0x91, 0x83, 0x52, 0x06, 0x35, 0x7A, 0xC1, 0xDA, 0x6B, 0xF1, 0x60, 0x9F, 0x18 - }, - .size = sizeof(g_ncaKeyset.nca_main_signature_moduli_dev[NcaSignatureKeyGeneration_Since100NUP]), - .dst = g_ncaKeyset.nca_main_signature_moduli_dev[NcaSignatureKeyGeneration_Since100NUP], - .mandatory_func = &keysIsDevelopmentModulus1xMandatory - }, - { - .name = "nca_main_signature_modulus_dev_01", - .hash = { - 0x56, 0xF5, 0x06, 0xEF, 0x8E, 0xCA, 0x2A, 0x29, 0x6F, 0x65, 0x45, 0xE1, 0x87, 0x60, 0x01, 0x11, - 0xBC, 0xC7, 0x38, 0x56, 0x99, 0x16, 0xAD, 0xA5, 0xDD, 0x89, 0xF2, 0xE9, 0xAB, 0x28, 0x5B, 0x18 - }, - .size = sizeof(g_ncaKeyset.nca_main_signature_moduli_dev[NcaSignatureKeyGeneration_Since900NUP]), - .dst = g_ncaKeyset.nca_main_signature_moduli_dev[NcaSignatureKeyGeneration_Since900NUP], - .mandatory_func = &keysIsDevelopmentModulus9xMandatory - }, - { - .name = "nca_kaek_application_source", - .hash = { - 0x04, 0xAD, 0x66, 0x14, 0x3C, 0x72, 0x6B, 0x2A, 0x13, 0x9F, 0xB6, 0xB2, 0x11, 0x28, 0xB4, 0x6F, - 0x56, 0xC5, 0x53, 0xB2, 0xB3, 0x88, 0x71, 0x10, 0x30, 0x42, 0x98, 0xD8, 0xD0, 0x09, 0x2D, 0x9E - }, - .size = sizeof(g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Application]), - .dst = g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Application], - .mandatory_func = NULL - }, - { - .name = "nca_kaek_ocean_source", - .hash = { - 0xFD, 0x43, 0x40, 0x00, 0xC8, 0xFF, 0x2B, 0x26, 0xF8, 0xE9, 0xA9, 0xD2, 0xD2, 0xC1, 0x2F, 0x6B, - 0xE5, 0x77, 0x3C, 0xBB, 0x9D, 0xC8, 0x63, 0x00, 0xE1, 0xBD, 0x99, 0xF8, 0xEA, 0x33, 0xA4, 0x17 - }, - .size = sizeof(g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Ocean]), - .dst = g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_Ocean], - .mandatory_func = NULL - }, - { - .name = "nca_kaek_system_source", - .hash = { - 0x1F, 0x17, 0xB1, 0xFD, 0x51, 0xAD, 0x1C, 0x23, 0x79, 0xB5, 0x8F, 0x15, 0x2C, 0xA4, 0x91, 0x2E, - 0xC2, 0x10, 0x64, 0x41, 0xE5, 0x17, 0x22, 0xF3, 0x87, 0x00, 0xD5, 0x93, 0x7A, 0x11, 0x62, 0xF7 - }, - .size = sizeof(g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_System]), - .dst = g_ncaKeyset.nca_kaek_sources[NcaKeyAreaEncryptionKeyIndex_System], - .mandatory_func = NULL - } - } -}; - -static KeysMemoryInfo g_fsDataMemoryInfo = { - .location = { - .program_id = FS_SYSMODULE_TID, - .mask = MemoryProgramSegmentType_Data, - .data = NULL, - .data_size = 0 - }, - .key_count = 1, - .keys = { - { - .name = "nca_header_key_source", - .hash = { - 0x8F, 0x78, 0x3E, 0x46, 0x85, 0x2D, 0xF6, 0xBE, 0x0B, 0xA4, 0xE1, 0x92, 0x73, 0xC4, 0xAD, 0xBA, - 0xEE, 0x16, 0x38, 0x00, 0x43, 0xE1, 0xB8, 0xC4, 0x18, 0xC4, 0x08, 0x9A, 0x8B, 0xD6, 0x4A, 0xA6 - }, - .size = sizeof(g_ncaKeyset.nca_header_key_source), - .dst = g_ncaKeyset.nca_header_key_source, - .mandatory_func = NULL - } - } -}; - -static KeysGameCardKeyset g_gameCardKeyset = { - .gc_cardinfo_kek_source = { 0xDE, 0xC6, 0x3F, 0x6A, 0xBF, 0x37, 0x72, 0x0B, 0x7E, 0x54, 0x67, 0x6A, 0x2D, 0xEF, 0xDD, 0x97 }, - .gc_cardinfo_key_prod_source = { 0xF4, 0x92, 0x06, 0x52, 0xD6, 0x37, 0x70, 0xAF, 0xB1, 0x9C, 0x6F, 0x63, 0x09, 0x01, 0xF6, 0x29 }, - .gc_cardinfo_key_dev_source = { 0x0B, 0x7D, 0xBB, 0x2C, 0xCF, 0x64, 0x1A, 0xF4, 0xD7, 0x38, 0x81, 0x3F, 0x0C, 0x33, 0xF4, 0x1C }, - .gc_cardinfo_key_prod = {0}, - .gc_cardinfo_key_dev = {0} -}; +static bool g_latestMasterKeyAvailable = false; bool keysLoadKeyset(void) { @@ -316,54 +125,47 @@ bool keysLoadKeyset(void) ret = g_keysetLoaded; if (ret) break; - /* Retrieve FS .rodata keys. */ - if (!keysRetrieveKeysFromProgramMemory(&g_fsRodataMemoryInfo)) + /* Get eTicket RSA device key. */ + Result rc = setcalGetEticketDeviceKey(&g_eTicketRsaDeviceKey); + if (R_FAILED(rc)) { - LOG_MSG_ERROR("Unable to retrieve keys from FS .rodata segment!"); + LOG_MSG_ERROR("setcalGetEticketDeviceKey failed! (0x%X).", rc); break; } - /* Retrieve FS .data keys. */ - if (!keysRetrieveKeysFromProgramMemory(&g_fsDataMemoryInfo)) + /* Read data from the Lockpick_RCM keys file. */ + if (!keysReadKeysFromFile()) break; + + /* Derive master keys. */ + if (!keysDeriveMasterKeys()) { - LOG_MSG_ERROR("Unable to retrieve keys from FS .data segment!"); + LOG_MSG_ERROR("Failed to derive master keys!"); break; } /* Derive NCA header key. */ - if (!keysDeriveNcaHeaderKey()) + if (!keysDeriveNcaHeaderKey()) break; + + /* Derive per-generation keys. */ + if (!keysDerivePerGenerationKeys()) break; + + /* Derive gamecard CardInfo key */ + if (!keysDeriveGcCardInfoKey()) { - LOG_MSG_ERROR("Unable to derive NCA header key!"); + LOG_MSG_ERROR("Failed to derive gamecard CardInfo key!"); break; } - /* Derive sealed NCA KAEKs. */ - if (!keysDeriveSealedNcaKeyAreaEncryptionKeys()) - { - LOG_MSG_ERROR("Unable to derive sealed NCA KAEKs!"); - break; - } - - /* Read additional keys from the keys file. */ - if (!keysReadKeysFromFile()) break; - - /* Verify loaded Lockpick_RCM data. */ - if (!keysVerifyLockpickRcmData()) break; - /* Get decrypted eTicket RSA device key. */ if (!keysGetDecryptedEticketRsaDeviceKey()) break; - /* Derive gamecard keys. */ - if (!keysDeriveGameCardKeys()) break; - /* Update flags. */ ret = g_keysetLoaded = true; } #if LOG_LEVEL == LOG_LEVEL_DEBUG - LOG_DATA_DEBUG(&g_ncaKeyset, sizeof(KeysNcaKeyset), "NCA keyset dump:"); LOG_DATA_DEBUG(&g_eTicketRsaDeviceKey, sizeof(SetCalRsa2048DeviceKey), "eTicket RSA device key dump:"); - LOG_DATA_DEBUG(&g_gameCardKeyset, sizeof(KeysGameCardKeyset), "Gamecard keyset dump:"); + LOG_DATA_DEBUG(&g_nxKeyset, sizeof(KeysNxKeyset), "NX keyset dump:"); #endif return ret; @@ -375,73 +177,12 @@ const u8 *keysGetNcaHeaderKey(void) SCOPED_LOCK(&g_keysetMutex) { - if (g_keysetLoaded) ret = (const u8*)(g_ncaKeyset.nca_header_key); + if (g_keysetLoaded) ret = (const u8*)(g_nxKeyset.nca_header_key); } return ret; } -const u8 *keysGetNcaMainSignatureModulus(u8 key_generation) -{ - if (key_generation > NcaSignatureKeyGeneration_Current) - { - LOG_MSG_ERROR("Unsupported key generation value! (0x%02X).", key_generation); - return NULL; - } - - bool dev_unit = utilsIsDevelopmentUnit(); - const u8 *ret = NULL, null_modulus[RSA2048_PUBKEY_SIZE] = {0}; - - SCOPED_LOCK(&g_keysetMutex) - { - if (!g_keysetLoaded) break; - - ret = (const u8*)(dev_unit ? g_ncaKeyset.nca_main_signature_moduli_dev[key_generation] : g_ncaKeyset.nca_main_signature_moduli_prod[key_generation]); - - if (!memcmp(ret, null_modulus, RSA2048_PUBKEY_SIZE)) - { - LOG_MSG_ERROR("%s NCA header main signature modulus 0x%02X unavailable.", dev_unit ? "Development" : "Retail", key_generation); - ret = NULL; - } - } - - return ret; -} - -bool keysDecryptNcaKeyAreaEntry(u8 kaek_index, u8 key_generation, void *dst, const void *src) -{ - bool ret = false; - u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation); - - if (kaek_index >= NcaKeyAreaEncryptionKeyIndex_Count) - { - LOG_MSG_ERROR("Invalid KAEK index! (0x%02X).", kaek_index); - goto end; - } - - if (key_gen_val >= NcaKeyGeneration_Max) - { - LOG_MSG_ERROR("Invalid key generation value! (0x%02X).", key_gen_val); - goto end; - } - - if (!dst || !src) - { - LOG_MSG_ERROR("Invalid destination/source pointer."); - goto end; - } - - SCOPED_LOCK(&g_keysetMutex) - { - if (!g_keysetLoaded) break; - Result rc = splCryptoGenerateAesKey(g_ncaKeyset.nca_kaek_sealed[kaek_index][key_gen_val], src, dst); - if (!(ret = R_SUCCEEDED(rc))) LOG_MSG_ERROR("splCryptoGenerateAesKey failed! (0x%X).", rc); - } - -end: - return ret; -} - const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation) { const u8 *ret = NULL; @@ -453,15 +194,23 @@ const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation) goto end; } - if (key_gen_val >= NcaKeyGeneration_Max) + if (key_generation >= NcaKeyGeneration_Max) { - LOG_MSG_ERROR("Invalid key generation value! (0x%02X).", key_gen_val); + LOG_MSG_ERROR("Invalid key generation value! (0x%02X).", key_generation); goto end; } SCOPED_LOCK(&g_keysetMutex) { - if (g_keysetLoaded) ret = (const u8*)(g_ncaKeyset.nca_kaek[kaek_index][key_gen_val]); + if (!g_keysetLoaded) break; + + ret = (const u8*)(g_nxKeyset.nca_kaek[kaek_index][key_gen_val]); + + if (keysIsKeyEmpty(ret)) + { + LOG_MSG_ERROR("NCA KAEK for type %u and generation %u unavailable.", kaek_index, key_gen_val); + ret = NULL; + } } end: @@ -509,15 +258,23 @@ const u8 *keysGetTicketCommonKey(u8 key_generation) const u8 *ret = NULL; u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation); - if (key_gen_val >= NcaKeyGeneration_Max) + if (key_generation >= NcaKeyGeneration_Max) { - LOG_MSG_ERROR("Invalid key generation value! (0x%02X).", key_gen_val); + LOG_MSG_ERROR("Invalid key generation value! (0x%02X).", key_generation); goto end; } SCOPED_LOCK(&g_keysetMutex) { - if (g_keysetLoaded) ret = (const u8*)(g_ncaKeyset.ticket_common_keys[key_gen_val]); + if (!g_keysetLoaded) break; + + ret = (const u8*)(g_nxKeyset.ticket_common_keys[key_gen_val]); + + if (keysIsKeyEmpty(ret)) + { + LOG_MSG_ERROR("Ticket common key for generation %u unavailable.", key_gen_val); + ret = NULL; + } } end: @@ -530,145 +287,16 @@ const u8 *keysGetGameCardInfoKey(void) SCOPED_LOCK(&g_keysetMutex) { - if (g_keysetLoaded) ret = (const u8*)(utilsIsDevelopmentUnit() ? g_gameCardKeyset.gc_cardinfo_key_dev : g_gameCardKeyset.gc_cardinfo_key_prod); + if (g_keysetLoaded) ret = (const u8*)(g_nxKeyset.gc_cardinfo_key); } return ret; } -static bool keysIsProductionModulus1xMandatory(void) +static bool keysIsKeyEmpty(const void *key) { - return !utilsIsDevelopmentUnit(); -} - -static bool keysIsProductionModulus9xMandatory(void) -{ - return (!utilsIsDevelopmentUnit() && hosversionAtLeast(9, 0, 0)); -} - -static bool keysIsDevelopmentModulus1xMandatory(void) -{ - return utilsIsDevelopmentUnit(); -} - -static bool keysIsDevelopmentModulus9xMandatory(void) -{ - return (utilsIsDevelopmentUnit() && hosversionAtLeast(9, 0, 0)); -} - -static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info) -{ - if (!info || !info->key_count) - { - LOG_MSG_ERROR("Invalid parameters!"); - return false; - } - - u8 tmp_hash[SHA256_HASH_SIZE]; - bool success = false; - - if (!memRetrieveProgramMemorySegment(&(info->location))) return false; - - for(u32 i = 0; i < info->key_count; i++) - { - KeysMemoryKey *key = &(info->keys[i]); - bool found = false, mandatory = (key->mandatory_func != NULL ? key->mandatory_func() : true); - - /* Skip key if it's not mandatory. */ - if (!mandatory) continue; - - /* Check destination pointer. */ - if (!key->dst) - { - LOG_MSG_ERROR("Invalid destination pointer for key \"%s\" in program %016lX!", key->name, info->location.program_id); - goto end; - } - - /* Hash every key length-sized byte chunk in the process memory buffer until a match is found. */ - for(u64 j = 0; j < info->location.data_size; j++) - { - if ((info->location.data_size - j) < key->size) break; - - sha256CalculateHash(tmp_hash, info->location.data + j, key->size); - if (!memcmp(tmp_hash, key->hash, SHA256_HASH_SIZE)) - { - /* Jackpot. */ - memcpy(key->dst, info->location.data + j, key->size); - found = true; - break; - } - } - - if (!found) - { - LOG_MSG_ERROR("Unable to locate key \"%s\" in process memory from program %016lX!", key->name, info->location.program_id); - goto end; - } - } - - success = true; - -end: - memFreeMemoryLocation(&(info->location)); - - return success; -} - -static bool keysDeriveNcaHeaderKey(void) -{ - /* Derive nca_header_key (first half) from nca_header_kek_source and nca_header_key_source. */ - if (!keysGenerateAesKey(g_ncaKeyset.nca_header_kek_source, g_ncaKeyset.nca_header_key_source, 0, 0, g_ncaKeyset.nca_header_key)) - { - LOG_MSG_ERROR("keysGenerateAesKey failed! (#1)."); - return false; - } - - /* Derive nca_header_key (second half) from nca_header_kek_source and nca_header_key_source. */ - if (!keysGenerateAesKey(g_ncaKeyset.nca_header_kek_source, g_ncaKeyset.nca_header_key_source + AES_128_KEY_SIZE, 0, 0, g_ncaKeyset.nca_header_key + AES_128_KEY_SIZE)) - { - LOG_MSG_ERROR("keysGenerateAesKey failed! (#2)."); - return false; - } - - return true; -} - -static bool keysDeriveSealedNcaKeyAreaEncryptionKeys(void) -{ - Result rc = 0; - u32 key_cnt = 0; - u8 highest_key_gen = 0; - bool success = false; - - for(u8 i = 0; i < NcaKeyAreaEncryptionKeyIndex_Count; i++) - { - /* Get pointer to current KAEK source. */ - const u8 *nca_kaek_source = (const u8*)(g_ncaKeyset.nca_kaek_sources[i]); - - for(u8 j = 1; j <= NcaKeyGeneration_Max; j++) - { - /* Get pointer to current sealed KAEK. */ - u8 key_gen_val = (j - 1); - u8 *nca_kaek_sealed = g_ncaKeyset.nca_kaek_sealed[i][key_gen_val]; - - /* Derive sealed KAEK using the current KAEK source and key generation. */ - rc = splCryptoGenerateAesKek(nca_kaek_source, j, 0, nca_kaek_sealed); - if (R_FAILED(rc)) - { - LOG_MSG_DEBUG("splCryptoGenerateAesKek failed for KAEK index %u and key generation %u! (0x%X).", i, (j <= 1 ? 0 : j), rc); - break; - } - - /* Update derived key count and highest key generation value. */ - key_cnt++; - if (key_gen_val > highest_key_gen) highest_key_gen = key_gen_val; - } - } - - success = (key_cnt > 0); - if (success) LOG_MSG_INFO("Derived %u sealed NCA KAEK(s) (%u key generation[s]).", key_cnt, highest_key_gen + 1); - - return success; + const u8 null_key[AES_128_KEY_SIZE] = {0}; + return (memcmp(key, null_key, AES_128_KEY_SIZE) == 0); } /** @@ -941,9 +569,13 @@ static bool keysReadKeysFromFile(void) FILE *keys_file = NULL; char *line = NULL, *key = NULL, *value = NULL; char test_name[0x40] = {0}; - bool eticket_rsa_kek_available = false; + const char *keys_file_path = (utilsIsDevelopmentUnit() ? DEV_KEYS_FILE_PATH : PROD_KEYS_FILE_PATH); + 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; + keys_file = fopen(keys_file_path, "rb"); if (!keys_file) { @@ -957,9 +589,9 @@ static bool keysReadKeysFromFile(void) decl; \ } -#define PARSE_HEX_KEY_WITH_INDEX(name, out) \ - snprintf(test_name, sizeof(test_name), "%s_%02x", name, i); \ - PARSE_HEX_KEY(test_name, out, break); +#define PARSE_HEX_KEY_WITH_INDEX(name, idx, out, decl) \ + snprintf(test_name, sizeof(test_name), "%s_%02x", name, idx); \ + PARSE_HEX_KEY(test_name, out, decl); while(true) { @@ -971,20 +603,35 @@ static bool keysReadKeysFromFile(void) /* Ignore malformed or empty lines. */ if (ret != 0 || !key || !value) continue; - PARSE_HEX_KEY("eticket_rsa_kek", g_ncaKeyset.eticket_rsa_kek, eticket_rsa_kek_available = true; continue); - - /* This only appears on consoles that use the new PRODINFO key generation scheme. */ - PARSE_HEX_KEY("eticket_rsa_kek_personalized", g_ncaKeyset.eticket_rsa_kek_personalized, eticket_rsa_kek_available = true; continue); - - for(u32 i = 0; i < NcaKeyGeneration_Max; i++) + if (is_mariko) { - PARSE_HEX_KEY_WITH_INDEX("titlekek", g_ncaKeyset.ticket_common_keys[i]); + /* Parse Mariko KEK. */ + /* This will only appear on Mariko units. */ + if (!mariko_kek_available) + { + PARSE_HEX_KEY("mariko_kek", g_nxKeyset.mariko_kek, mariko_kek_available = true; continue); + } + } else { + /* Parse TSEC root key. */ + /* This will only appear on Erista units. */ + if (!tsec_root_key_available) + { + 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("key_area_key_application", g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_Application][i]); + /* Parse eTicket RSA device KEK. */ + /* 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_WITH_INDEX("key_area_key_ocean", g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_Ocean][i]); - - PARSE_HEX_KEY_WITH_INDEX("key_area_key_system", g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_System][i]); + /* Parse master keys, starting with the last known one. */ + for(u8 i = NcaKeyGeneration_Current; 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); } } @@ -996,6 +643,7 @@ static bool keysReadKeysFromFile(void) fclose(keys_file); + /* Bail out if we didn't retrieve a single key. */ if (key_count) { LOG_MSG_INFO("Loaded %u key(s) from \"%s\".", key_count, keys_file_path); @@ -1004,72 +652,156 @@ 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; + } + } + } + if (!eticket_rsa_kek_available) { - LOG_MSG_ERROR("\"eticket_rsa_kek\" unavailable in \"%s\"!", keys_file_path); + LOG_MSG_ERROR("eTicket RSA KEK unavailable in \"%s\"!", keys_file_path); return false; } return true; } -static bool keysVerifyLockpickRcmData(void) +static bool keysDeriveMasterKeys(void) { - u8 hash[SHA256_HASH_SIZE] = {0}; - u8 dev_unit = (utilsIsDevelopmentUnit() ? 1 : 0); - size_t block_size = (NcaKeyGeneration_Current * AES_128_KEY_SIZE); + u8 tmp[AES_128_KEY_SIZE] = {0}; + u8 latest_mkey_index = (NcaKeyGeneration_Current - 1); + bool is_dev = utilsIsDevelopmentUnit(); - /* Verify loaded NCA key area encryption keys. */ - for(u8 i = 0; i < NcaKeyAreaEncryptionKeyIndex_Count; i++) + /* Only derive the latest master key if it hasn't been populated already. */ + if (!g_latestMasterKeyAvailable) { - sha256CalculateHash(hash, g_ncaKeyset.nca_kaek[i], block_size); - if (memcmp(hash, g_ncaKaekBlockHashes[dev_unit][i], SHA256_HASH_SIZE) != 0) + if (utilsIsMarikoUnit()) { - LOG_MSG_ERROR("NCA KAEK block #%u checksum mismatch! (%s, keygen %u).", i, dev_unit ? "dev" : "prod", NcaKeyGeneration_Current); - 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 { + /* 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); } - /* Verify loaded ticket common keys. */ - sha256CalculateHash(hash, g_ncaKeyset.ticket_common_keys, block_size); - if (memcmp(hash, g_ticketCommonKeysBlockHashes[dev_unit], SHA256_HASH_SIZE) != 0) + /* 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], \ + 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); +} + +static bool keysDeriveNcaHeaderKey(void) +{ + u8 nca_header_kek[AES_128_KEY_SIZE] = {0}; + + SmcGenerateAesKekOption option = {0}; + smcPrepareGenerateAesKekOption(false, SmcKeyType_Default, SmcSealKey_LoadAesKey, &option); + + /* Derive nca_header_kek using g_ncaHeaderKekSource and master key 00. */ + if (!keysGenerateAesKek(g_ncaHeaderKekSource, NcaKeyGeneration_Since100NUP, option, nca_header_kek)) { - LOG_MSG_ERROR("Ticket common keys block checksum mismatch! (%s, keygen %u).", dev_unit ? "dev" : "prod", NcaKeyGeneration_Current); + LOG_MSG_ERROR("Failed to derive NCA header KEK!"); + return false; + } + + /* Derive nca_header_key (first half) from nca_header_kek and g_ncaHeaderKeySource. */ + if (!keysGenerateAesKey(nca_header_kek, g_ncaHeaderKeySource, g_nxKeyset.nca_header_key)) + { + LOG_MSG_ERROR("Failed to derive NCA header key! (#1)."); + return false; + } + + /* Derive nca_header_key (second half) from nca_header_kek and g_ncaHeaderKeySource. */ + if (!keysGenerateAesKey(nca_header_kek, g_ncaHeaderKeySource + AES_128_KEY_SIZE, g_nxKeyset.nca_header_key + AES_128_KEY_SIZE)) + { + LOG_MSG_ERROR("Failed to derive NCA header key! (#2)."); return false; } return true; } +static bool keysDerivePerGenerationKeys(void) +{ + SmcGenerateAesKekOption option = {0}; + smcPrepareGenerateAesKekOption(false, SmcKeyType_Default, SmcSealKey_LoadAesKey, &option); + + bool success = true; + + for(u8 i = 1; i <= NcaKeyGeneration_Max; i++) + { + u8 key_gen_val = (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])) + { + //LOG_MSG_DEBUG("Master key %02X unavailable.", key_gen_val); + 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])) + { + LOG_MSG_DEBUG("Failed to derive NCA KAEK for type %u and generation %u!", j, key_gen_val); + success = false; + break; + } + } + + 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); + } + + return success; +} + +static bool keysDeriveGcCardInfoKey(void) +{ + SmcGenerateAesKekOption option = {0}; + const u8 *key_src = (utilsIsDevelopmentUnit() ? g_gcCardInfoKeySourceDev : g_gcCardInfoKeySourceProd); + smcPrepareGenerateAesKekOption(false, SmcKeyType_Default, SmcSealKey_LoadAesKey, &option); + return keysGenerateAesKeyFromAesKek(g_gcCardInfoKekSource, NcaKeyGeneration_Since100NUP, option, key_src, g_nxKeyset.gc_cardinfo_key); +} + static bool keysGetDecryptedEticketRsaDeviceKey(void) { - Result rc = 0; u32 public_exponent = 0; - const u8 *eticket_rsa_kek = NULL; - EticketRsaDeviceKey *eticket_rsa_key = NULL; Aes128CtrContext eticket_aes_ctx = {0}; - const u8 nullkey[AES_128_KEY_SIZE] = {0}; - - /* Get eTicket RSA device key. */ - rc = setcalGetEticketDeviceKey(&g_eTicketRsaDeviceKey); - if (R_FAILED(rc)) - { - LOG_MSG_ERROR("setcalGetEticketDeviceKey failed! (0x%X).", rc); - return false; - } - - /* Get eTicket RSA device key encryption key. */ - eticket_rsa_kek = (const u8*)(g_eTicketRsaDeviceKey.generation > 0 ? g_ncaKeyset.eticket_rsa_kek_personalized : g_ncaKeyset.eticket_rsa_kek); - - if (!memcmp(eticket_rsa_kek, nullkey, sizeof(nullkey))) - { - LOG_MSG_ERROR("Empty \"eticket_rsa_kek\" key entry! (0x%X).", g_eTicketRsaDeviceKey.generation); - return false; - } + EticketRsaDeviceKey *eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key; /* Decrypt eTicket RSA device key. */ - eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key; - aes128CtrContextCreate(&eticket_aes_ctx, eticket_rsa_kek, eticket_rsa_key->ctr); + aes128CtrContextCreate(&eticket_aes_ctx, g_nxKeyset.eticket_rsa_kek, eticket_rsa_key->ctr); aes128CtrCrypt(&eticket_aes_ctx, &(eticket_rsa_key->private_exponent), &(eticket_rsa_key->private_exponent), sizeof(EticketRsaDeviceKey) - sizeof(eticket_rsa_key->ctr)); /* Public exponent value must be 0x10001. */ @@ -1131,52 +863,85 @@ static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void return true; } -static bool keysDeriveGameCardKeys(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) { - /* Derive gc_cardinfo_key_prod from gc_cardinfo_kek_source and gc_cardinfo_key_prod_source. */ - if (!keysGenerateAesKey(g_gameCardKeyset.gc_cardinfo_kek_source, g_gameCardKeyset.gc_cardinfo_key_prod_source, 0, 0, g_gameCardKeyset.gc_cardinfo_key_prod)) - { - LOG_MSG_ERROR("keysGenerateAesKey failed! (prod)."); - return false; - } + 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; - /* Derive gc_cardinfo_key_dev from gc_cardinfo_kek_source and gc_cardinfo_key_dev_source. */ - if (!keysGenerateAesKey(g_gameCardKeyset.gc_cardinfo_kek_source, g_gameCardKeyset.gc_cardinfo_key_dev_source, 0, 0, g_gameCardKeyset.gc_cardinfo_key_dev)) - { - LOG_MSG_ERROR("keysGenerateAesKey failed! (dev)."); - return false; - } - - return true; -} - -/* Wrapper for GenerateAesKek + GenerateAesKey SMC AES engine calls. */ -static bool keysGenerateAesKey(const u8 *kek_source, const u8 *key_source, u32 key_generation, u32 option, u8 *out_key) -{ - if (!kek_source || !key_source || key_generation >= NcaKeyGeneration_Max || !out_key) + 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) { LOG_MSG_ERROR("Invalid parameters!"); return false; } - Result rc = 0; - u8 sealed_kek[AES_128_KEY_SIZE] = {0}; + if (key_generation) key_generation--; - /* Derive sealed_kek from kek_source. */ - rc = splCryptoGenerateAesKek(kek_source, key_generation, option, sealed_kek); - if (R_FAILED(rc)) + u8 kekek_src[AES_128_KEY_SIZE] = {0}, kekek[AES_128_KEY_SIZE] = {0}; + const u8 *mkey = g_nxKeyset.master_keys[key_generation]; + + /* Make sure this master key is available. */ + if (keysIsKeyEmpty(mkey)) { - LOG_MSG_ERROR("splCryptoGenerateAesKek failed! (0x%X).", rc); + LOG_MSG_ERROR("Master key %02X unavailable!", key_generation); return false; } - /* Derive out_key from sealed_kek and key_source. */ - rc = splCryptoGenerateAesKey(sealed_kek, key_source, out_key); - if (R_FAILED(rc)) - { - LOG_MSG_ERROR("splCryptoGenerateAesKey failed! (0x%X).", rc); - return false; - } + /* Derive the KEKEK source using hardcoded data. */ + for(u8 i = 0; i < AES_128_KEY_SIZE; i++) kekek_src[i] = (g_smcKeyTypeSources[key_type_idx][i] ^ g_smcSealKeyMasks[seal_key_idx][i]); + + /* Derive the KEKEK using the KEKEK source and the master key. */ + aes128EcbCrypt(kekek, kekek_src, mkey, false); + + /* Derive the KEK using the provided KEK source and the derived KEKEK. */ + aes128EcbCrypt(out_kek, kek_src, kekek, false); return true; } + +/* Based on splCryptoLoadAesKey(). Excludes key sealing shenanigans. */ +static bool keysLoadAesKey(const u8 *kek, const u8 *key_src, u8 *out_key) +{ + if (!kek || !key_src || !out_key) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + aes128EcbCrypt(out_key, key_src, kek, false); + + return true; +} + +/* Based on splCryptoGenerateAesKey(). Excludes key sealing shenanigans. */ +static bool keysGenerateAesKey(const u8 *kek, const u8 *key_src, u8 *out_key) +{ + if (!kek || !key_src || !out_key) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + u8 aes_key[AES_128_KEY_SIZE] = {0}; + + keysLoadAesKey(kek, g_aesKeyGenerationSource, aes_key); + aes128EcbCrypt(out_key, key_src, aes_key, false); + + return true; +} + +/* Wrapper for keysGenerateAesKek() + keysLoadAesKey() to generate a single usable AES key in one shot. */ +static bool keysLoadAesKeyFromAesKek(const u8 *kek_src, u8 key_generation, SmcGenerateAesKekOption option, const u8 *key_src, u8 *out_key) +{ + u8 kek[AES_128_KEY_SIZE] = {0}; + return (keysGenerateAesKek(kek_src, key_generation, option, kek) && keysLoadAesKey(kek, key_src, out_key)); +} + +/* Wrapper for keysGenerateAesKek() + keysGenerateAesKey() to generate a single usable AES key in one shot. */ +static bool keysGenerateAesKeyFromAesKek(const u8 *kek_src, u8 key_generation, SmcGenerateAesKekOption option, const u8 *key_src, u8 *out_key) +{ + u8 kek[AES_128_KEY_SIZE] = {0}; + return (keysGenerateAesKek(kek_src, key_generation, option, kek) && keysGenerateAesKey(kek, key_src, out_key)); +} diff --git a/source/core/nca.c b/source/core/nca.c index d22ac8f..588708a 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -34,22 +34,103 @@ static u8 *g_ncaCryptoBuffer = NULL; static Mutex g_ncaCryptoBufferMutex = 0; +/// Used to verify the NCA header main signature. +static const u8 g_ncaHeaderMainSignaturePublicExponent[3] = { 0x01, 0x00, 0x01 }; + +/// RSA-2048-PSS moduli used to verify the main signature from NCA headers with retail crypto. Found in the .rodata segment from the FS sysmodule. +/// TODO: update on signature keygen changes. +static const u8 g_ncaHeaderMainSignatureModuliProd[NcaSignatureKeyGeneration_Max][RSA2048_PUBKEY_SIZE] = { + { + 0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F, + 0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58, + 0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16, + 0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2, + 0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF, + 0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67, + 0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26, + 0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C, + 0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE, + 0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86, + 0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35, + 0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B, + 0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29, + 0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40, + 0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81, + 0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03 + }, + { + 0xAD, 0xE3, 0xE1, 0xFA, 0x04, 0x35, 0xE5, 0xB6, 0xDD, 0x49, 0xEA, 0x89, 0x29, 0xB1, 0xFF, 0xB6, + 0x43, 0xDF, 0xCA, 0x96, 0xA0, 0x4A, 0x13, 0xDF, 0x43, 0xD9, 0x94, 0x97, 0x96, 0x43, 0x65, 0x48, + 0x70, 0x58, 0x33, 0xA2, 0x7D, 0x35, 0x7B, 0x96, 0x74, 0x5E, 0x0B, 0x5C, 0x32, 0x18, 0x14, 0x24, + 0xC2, 0x58, 0xB3, 0x6C, 0x22, 0x7A, 0xA1, 0xB7, 0xCB, 0x90, 0xA7, 0xA3, 0xF9, 0x7D, 0x45, 0x16, + 0xA5, 0xC8, 0xED, 0x8F, 0xAD, 0x39, 0x5E, 0x9E, 0x4B, 0x51, 0x68, 0x7D, 0xF8, 0x0C, 0x35, 0xC6, + 0x3F, 0x91, 0xAE, 0x44, 0xA5, 0x92, 0x30, 0x0D, 0x46, 0xF8, 0x40, 0xFF, 0xD0, 0xFF, 0x06, 0xD2, + 0x1C, 0x7F, 0x96, 0x18, 0xDC, 0xB7, 0x1D, 0x66, 0x3E, 0xD1, 0x73, 0xBC, 0x15, 0x8A, 0x2F, 0x94, + 0xF3, 0x00, 0xC1, 0x83, 0xF1, 0xCD, 0xD7, 0x81, 0x88, 0xAB, 0xDF, 0x8C, 0xEF, 0x97, 0xDD, 0x1B, + 0x17, 0x5F, 0x58, 0xF6, 0x9A, 0xE9, 0xE8, 0xC2, 0x2F, 0x38, 0x15, 0xF5, 0x21, 0x07, 0xF8, 0x37, + 0x90, 0x5D, 0x2E, 0x02, 0x40, 0x24, 0x15, 0x0D, 0x25, 0xB7, 0x26, 0x5D, 0x09, 0xCC, 0x4C, 0xF4, + 0xF2, 0x1B, 0x94, 0x70, 0x5A, 0x9E, 0xEE, 0xED, 0x77, 0x77, 0xD4, 0x51, 0x99, 0xF5, 0xDC, 0x76, + 0x1E, 0xE3, 0x6C, 0x8C, 0xD1, 0x12, 0xD4, 0x57, 0xD1, 0xB6, 0x83, 0xE4, 0xE4, 0xFE, 0xDA, 0xE9, + 0xB4, 0x3B, 0x33, 0xE5, 0x37, 0x8A, 0xDF, 0xB5, 0x7F, 0x89, 0xF1, 0x9B, 0x9E, 0xB0, 0x15, 0xB2, + 0x3A, 0xFE, 0xEA, 0x61, 0x84, 0x5B, 0x7D, 0x4B, 0x23, 0x12, 0x0B, 0x83, 0x12, 0xF2, 0x22, 0x6B, + 0xB9, 0x22, 0x96, 0x4B, 0x26, 0x0B, 0x63, 0x5E, 0x96, 0x57, 0x52, 0xA3, 0x67, 0x64, 0x22, 0xCA, + 0xD0, 0x56, 0x3E, 0x74, 0xB5, 0x98, 0x1F, 0x0D, 0xF8, 0xB3, 0x34, 0xE6, 0x98, 0x68, 0x5A, 0xAD + } +}; + +/// RSA-2048-PSS moduli used to verify the main signature from NCA headers with development crypto. Found in the .rodata segment from the FS sysmodule. +/// TODO: update on signature keygen changes. +static const u8 g_ncaHeaderMainSignatureModuliDev[NcaSignatureKeyGeneration_Max][RSA2048_PUBKEY_SIZE] = { + { + 0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4, + 0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49, + 0x85, 0xE5, 0x8A, 0x9B, 0xC1, 0x00, 0x9A, 0x6A, 0x8D, 0xD0, 0xEF, 0xCE, 0xFF, 0x86, 0xC8, 0x5C, + 0x5D, 0xE9, 0x53, 0x7B, 0x19, 0x2A, 0xA8, 0xC0, 0x22, 0xD1, 0xF3, 0x22, 0x0A, 0x50, 0xF2, 0x2B, + 0x65, 0x05, 0x1B, 0x9E, 0xEC, 0x61, 0xB5, 0x63, 0xA3, 0x6F, 0x3B, 0xBA, 0x63, 0x3A, 0x53, 0xF4, + 0x49, 0x2F, 0xCF, 0x03, 0xCC, 0xD7, 0x50, 0x82, 0x1B, 0x29, 0x4F, 0x08, 0xDE, 0x1B, 0x6D, 0x47, + 0x4F, 0xA8, 0xB6, 0x6A, 0x26, 0xA0, 0x83, 0x3F, 0x1A, 0xAF, 0x83, 0x8F, 0x0E, 0x17, 0x3F, 0xFE, + 0x44, 0x1C, 0x56, 0x94, 0x2E, 0x49, 0x83, 0x83, 0x03, 0xE9, 0xB6, 0xAD, 0xD5, 0xDE, 0xE3, 0x2D, + 0xA1, 0xD9, 0x66, 0x20, 0x5D, 0x1F, 0x5E, 0x96, 0x5D, 0x5B, 0x55, 0x0D, 0xD4, 0xB4, 0x77, 0x6E, + 0xAE, 0x1B, 0x69, 0xF3, 0xA6, 0x61, 0x0E, 0x51, 0x62, 0x39, 0x28, 0x63, 0x75, 0x76, 0xBF, 0xB0, + 0xD2, 0x22, 0xEF, 0x98, 0x25, 0x02, 0x05, 0xC0, 0xD7, 0x6A, 0x06, 0x2C, 0xA5, 0xD8, 0x5A, 0x9D, + 0x7A, 0xA4, 0x21, 0x55, 0x9F, 0xF9, 0x3E, 0xBF, 0x16, 0xF6, 0x07, 0xC2, 0xB9, 0x6E, 0x87, 0x9E, + 0xB5, 0x1C, 0xBE, 0x97, 0xFA, 0x82, 0x7E, 0xED, 0x30, 0xD4, 0x66, 0x3F, 0xDE, 0xD8, 0x1B, 0x4B, + 0x15, 0xD9, 0xFB, 0x2F, 0x50, 0xF0, 0x9D, 0x1D, 0x52, 0x4C, 0x1C, 0x4D, 0x8D, 0xAE, 0x85, 0x1E, + 0xEA, 0x7F, 0x86, 0xF3, 0x0B, 0x7B, 0x87, 0x81, 0x98, 0x23, 0x80, 0x63, 0x4F, 0x2F, 0xB0, 0x62, + 0xCC, 0x6E, 0xD2, 0x46, 0x13, 0x65, 0x2B, 0xD6, 0x44, 0x33, 0x59, 0xB5, 0x8F, 0xB9, 0x4A, 0xA9 + }, + { + 0x9A, 0xBC, 0x88, 0xBD, 0x0A, 0xBE, 0xD7, 0x0C, 0x9B, 0x42, 0x75, 0x65, 0x38, 0x5E, 0xD1, 0x01, + 0xCD, 0x12, 0xAE, 0xEA, 0xE9, 0x4B, 0xDB, 0xB4, 0x5E, 0x36, 0x10, 0x96, 0xDA, 0x3D, 0x2E, 0x66, + 0xD3, 0x99, 0x13, 0x8A, 0xBE, 0x67, 0x41, 0xC8, 0x93, 0xD9, 0x3E, 0x42, 0xCE, 0x34, 0xCE, 0x96, + 0xFA, 0x0B, 0x23, 0xCC, 0x2C, 0xDF, 0x07, 0x3F, 0x3B, 0x24, 0x4B, 0x12, 0x67, 0x3A, 0x29, 0x36, + 0xA3, 0xAA, 0x06, 0xF0, 0x65, 0xA5, 0x85, 0xBA, 0xFD, 0x12, 0xEC, 0xF1, 0x60, 0x67, 0xF0, 0x8F, + 0xD3, 0x5B, 0x01, 0x1B, 0x1E, 0x84, 0xA3, 0x5C, 0x65, 0x36, 0xF9, 0x23, 0x7E, 0xF3, 0x26, 0x38, + 0x64, 0x98, 0xBA, 0xE4, 0x19, 0x91, 0x4C, 0x02, 0xCF, 0xC9, 0x6D, 0x86, 0xEC, 0x1D, 0x41, 0x69, + 0xDD, 0x56, 0xEA, 0x5C, 0xA3, 0x2A, 0x58, 0xB4, 0x39, 0xCC, 0x40, 0x31, 0xFD, 0xFB, 0x42, 0x74, + 0xF8, 0xEC, 0xEA, 0x00, 0xF0, 0xD9, 0x28, 0xEA, 0xFA, 0x2D, 0x00, 0xE1, 0x43, 0x53, 0xC6, 0x32, + 0xF4, 0xA2, 0x07, 0xD4, 0x5F, 0xD4, 0xCB, 0xAC, 0xCA, 0xFF, 0xDF, 0x84, 0xD2, 0x86, 0x14, 0x3C, + 0xDE, 0x22, 0x75, 0xA5, 0x73, 0xFF, 0x68, 0x07, 0x4A, 0xF9, 0x7C, 0x2C, 0xCC, 0xDE, 0x45, 0xB6, + 0x54, 0x82, 0x90, 0x36, 0x1F, 0x2C, 0x51, 0x96, 0xC5, 0x0A, 0x53, 0x5B, 0xF0, 0x8B, 0x4A, 0xAA, + 0x3B, 0x68, 0x97, 0x19, 0x17, 0x1F, 0x01, 0xB8, 0xED, 0xB9, 0x9A, 0x5E, 0x08, 0xC5, 0x20, 0x1E, + 0x6A, 0x09, 0xF0, 0xE9, 0x73, 0xA3, 0xBE, 0x10, 0x06, 0x02, 0xE9, 0xFB, 0x85, 0xFA, 0x5F, 0x01, + 0xAC, 0x60, 0xE0, 0xED, 0x7D, 0xB9, 0x49, 0xA8, 0x9E, 0x98, 0x7D, 0x91, 0x40, 0x05, 0xCF, 0xF9, + 0x1A, 0xFC, 0x40, 0x22, 0xA8, 0x96, 0x5B, 0xB0, 0xDC, 0x7A, 0xF5, 0xB7, 0xE9, 0x91, 0x4C, 0x49 + } +}; + /// Used to verify if the key area from a NCA0 is encrypted. static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = { 0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD, 0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB }; -/// Used to verify the NCA header main signature. -static const u8 g_ncaHeaderMainSignaturePublicExponent[3] = { 0x01, 0x00, 0x01 }; - /* Function prototypes. */ NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info); static bool ncaReadDecryptedHeader(NcaContext *ctx); -static bool ncaDecryptKeyArea(NcaContext *ctx); -static bool ncaEncryptKeyArea(NcaContext *ctx); +static bool ncaKeyAreaCrypt(NcaContext *ctx, bool encrypt); static bool ncaVerifyMainSignature(NcaContext *ctx); @@ -338,7 +419,7 @@ bool ncaRemoveTitleKeyCrypto(NcaContext *ctx) memcpy(ctx->decrypted_key_area.aes_ctr, ctx->titlekey, AES_128_KEY_SIZE); /* Encrypt NCA key area. */ - if (!ncaEncryptKeyArea(ctx)) + if (!ncaKeyAreaCrypt(ctx, true)) { LOG_MSG_ERROR("Error encrypting %s NCA \"%s\" key area!", titleGetNcmContentTypeName(ctx->content_type), ctx->content_id_str); return false; @@ -541,7 +622,7 @@ static bool ncaReadDecryptedHeader(NcaContext *ctx) ctx->valid_main_signature = ncaVerifyMainSignature(ctx); /* Decrypt NCA key area (if needed). */ - if (!ctx->rights_id_available && !ncaDecryptKeyArea(ctx)) + if (!ctx->rights_id_available && !ncaKeyAreaCrypt(ctx, false)) { LOG_MSG_ERROR("Error decrypting NCA \"%s\" key area!", ctx->content_id_str); return false; @@ -586,7 +667,7 @@ static bool ncaReadDecryptedHeader(NcaContext *ctx) return true; } -static bool ncaDecryptKeyArea(NcaContext *ctx) +static bool ncaKeyAreaCrypt(NcaContext *ctx, bool encrypt) { if (!ctx) { @@ -594,59 +675,19 @@ static bool ncaDecryptKeyArea(NcaContext *ctx) return false; } - const u8 null_key[AES_128_KEY_SIZE] = {0}; - - u8 key_count = NCA_KEY_AREA_USED_KEY_COUNT; - if (ctx->format_version == NcaVersion_Nca0) key_count--; - - /* Check if we're dealing with a NCA0 with a plaintext key area. */ - if (ncaIsVersion0KeyAreaEncrypted(ctx)) - { - memcpy(&(ctx->decrypted_key_area), &(ctx->header.encrypted_key_area), sizeof(NcaDecryptedKeyArea)); - return true; - } - - /* Clear decrypted key area. */ - memset(&(ctx->decrypted_key_area), 0, sizeof(NcaDecryptedKeyArea)); - - /* Process key area. */ - for(u8 i = 0; i < key_count; i++) - { - const u8 *src_key = ctx->header.encrypted_key_area.keys[i]; - u8 *dst_key = ctx->decrypted_key_area.keys[i]; - - /* Don't proceed if we're dealing with a null key. */ - if (!memcmp(src_key, null_key, AES_128_KEY_SIZE)) continue; - - /* Decrypt current key area entry. */ - if (!keysDecryptNcaKeyAreaEntry(ctx->header.kaek_index, ctx->key_generation, dst_key, src_key)) - { - LOG_MSG_ERROR("Failed to decrypt NCA key area entry #%u!", i); - return false; - } - } - - return true; -} - -static bool ncaEncryptKeyArea(NcaContext *ctx) -{ - if (!ctx) - { - LOG_MSG_ERROR("Invalid NCA context!"); - return false; - } + const u8 *src_key_area = (encrypt ? ((const u8*)&(ctx->decrypted_key_area)) : ((const u8*)&(ctx->header.encrypted_key_area))); + u8 *dst_key_area = (encrypt ? ((u8*)&(ctx->header.encrypted_key_area)) : ((u8*)&(ctx->decrypted_key_area))); + size_t dst_key_area_size = (encrypt ? sizeof(NcaEncryptedKeyArea) : sizeof(NcaDecryptedKeyArea)); u8 key_count = NCA_KEY_AREA_USED_KEY_COUNT; if (ctx->format_version == NcaVersion_Nca0) key_count--; const u8 *kaek = NULL, null_key[AES_128_KEY_SIZE] = {0}; - Aes128Context key_area_ctx = {0}; /* Check if we're dealing with a NCA0 with a plaintext key area. */ if (ncaIsVersion0KeyAreaEncrypted(ctx)) { - memcpy(&(ctx->header.encrypted_key_area), &(ctx->decrypted_key_area), sizeof(NcaDecryptedKeyArea)); + memcpy(dst_key_area, src_key_area, sizeof(NcaDecryptedKeyArea)); return true; } @@ -654,27 +695,24 @@ static bool ncaEncryptKeyArea(NcaContext *ctx) kaek = keysGetNcaKeyAreaEncryptionKey(ctx->header.kaek_index, ctx->key_generation); if (!kaek) { - LOG_MSG_ERROR("Unable to retrieve KAEK for KAEK index 0x%02X and key generation 0x%02X!", ctx->header.kaek_index, ctx->key_generation); + LOG_MSG_ERROR("Unable to retrieve KAEK for type %u and generation %u!", ctx->header.kaek_index, ctx->key_generation); return false; } - /* Clear encrypted key area. */ - memset(&(ctx->header.encrypted_key_area), 0, sizeof(NcaEncryptedKeyArea)); + /* Clear destination key area. */ + memset(dst_key_area, 0, dst_key_area_size); - /* Initialize AES-128-ECB encryption context using the retrieved KAEK. */ - aes128ContextCreate(&key_area_ctx, kaek, true); - - /* Process key area. */ + /* Process source key area. */ for(u8 i = 0; i < key_count; i++) { - const u8 *src_key = ctx->decrypted_key_area.keys[i]; - u8 *dst_key = ctx->header.encrypted_key_area.keys[i]; + const u8 *src_key = (src_key_area + (i * AES_128_KEY_SIZE)); + u8 *dst_key = (dst_key_area + (i * AES_128_KEY_SIZE)); /* Don't proceed if we're dealing with a null key. */ if (!memcmp(src_key, null_key, AES_128_KEY_SIZE)) continue; - /* Encrypt current key area entry. */ - aes128EncryptBlock(&key_area_ctx, dst_key, src_key); + /* Process current key area entry. */ + aes128EcbCrypt(dst_key, src_key, kaek, encrypt); } return true; @@ -688,9 +726,15 @@ static bool ncaVerifyMainSignature(NcaContext *ctx) return false; } + u8 key_generation = ctx->header.main_signature_key_generation; + if (key_generation > NcaSignatureKeyGeneration_Current) + { + LOG_MSG_ERROR("Unsupported key generation value! (0x%02X).", key_generation); + return false; + } + /* Retrieve modulus for the NCA main signature. */ - const u8 *modulus = keysGetNcaMainSignatureModulus(ctx->header.main_signature_key_generation); - if (!modulus) return false; + const u8 *modulus = (utilsIsDevelopmentUnit() ? g_ncaHeaderMainSignatureModuliDev[key_generation] : g_ncaHeaderMainSignatureModuliProd[key_generation]); /* Verify NCA signature. */ bool ret = rsa2048VerifySha256BasedPssSignature(&(ctx->header.magic), NCA_SIGNATURE_AREA_SIZE, ctx->header.main_signature, modulus, g_ncaHeaderMainSignaturePublicExponent, \ diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 06fce19..307ba41 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -57,6 +57,8 @@ static int g_nxLinkSocketFd = -1; static u8 g_customFirmwareType = UtilsCustomFirmwareType_Unknown; +static u8 g_productModel = SetSysProductModel_Invalid; + static bool g_isDevUnit = false; static AppletType g_programAppletType = AppletType_None; @@ -96,9 +98,9 @@ static void _utilsGetLaunchPath(int program_argc, const char **program_argv); static void _utilsGetCustomFirmwareType(void); -static bool _utilsIsDevelopmentUnit(void); +static bool _utilsGetProductModel(void); -static bool _utilsAppletModeCheck(void); +static bool _utilsIsDevelopmentUnit(void); static bool utilsMountEmmcBisSystemPartitionStorage(void); static void utilsUnmountEmmcBisSystemPartitionStorage(void); @@ -153,13 +155,16 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) if (g_customFirmwareType != UtilsCustomFirmwareType_Unknown) LOG_MSG_INFO("Detected %s CFW.", (g_customFirmwareType == UtilsCustomFirmwareType_Atmosphere ? "Atmosphère" : \ (g_customFirmwareType == UtilsCustomFirmwareType_SXOS ? "SX OS" : "ReiNX"))); - /* Check if we're not running under a development unit. */ + /* Get product model. */ + if (!_utilsGetProductModel()) break; + + /* Get development unit flag. */ if (!_utilsIsDevelopmentUnit()) break; - LOG_MSG_INFO("Running under %s unit.", g_isDevUnit ? "development" : "retail"); /* Get applet type. */ g_programAppletType = appletGetAppletType(); - LOG_MSG_INFO("Running under %s mode.", _utilsAppletModeCheck() ? "applet" : "title override"); + + LOG_MSG_INFO("Running under %s %s unit in %s mode.", g_isDevUnit ? "development" : "retail", utilsIsMarikoUnit() ? "Mariko" : "Erista", utilsIsAppletMode() ? "applet" : "title override"); /* Create output directories (SD card only). */ /* TODO: remove the APP_TITLE check whenever we're ready for a release. */ @@ -182,7 +187,7 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) } /* Initialize HTTP interface. */ - /* CURL must be initialized before starting any other threads. */ + /* cURL must be initialized before starting any other threads. */ if (!httpInitialize()) break; /* Initialize USB interface. */ @@ -235,7 +240,7 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) appletHook(&g_systemOverclockCookie, utilsOverclockSystemAppletHook, NULL); /* Enable video recording if we're running under title override mode. */ - if (!_utilsAppletModeCheck()) + if (!utilsIsAppletMode()) { bool flag = false; rc = appletIsGamePlayRecordingSupported(&flag); @@ -369,14 +374,19 @@ u8 utilsGetCustomFirmwareType(void) return g_customFirmwareType; } +bool utilsIsMarikoUnit(void) +{ + return (g_productModel > SetSysProductModel_Copper); +} + bool utilsIsDevelopmentUnit(void) { return g_isDevUnit; } -bool utilsAppletModeCheck(void) +bool utilsIsAppletMode(void) { - return _utilsAppletModeCheck(); + return (g_programAppletType > AppletType_Application && g_programAppletType < AppletType_SystemApplication); } FsStorage *utilsGetEmmcBisSystemPartitionStorage(void) @@ -1050,6 +1060,24 @@ static void _utilsGetCustomFirmwareType(void) g_customFirmwareType = (rnx_srv ? UtilsCustomFirmwareType_ReiNX : (tx_srv ? UtilsCustomFirmwareType_SXOS : UtilsCustomFirmwareType_Atmosphere)); } +static bool _utilsGetProductModel(void) +{ + Result rc = 0; + bool ret = false; + SetSysProductModel model = SetSysProductModel_Invalid; + + rc = setsysGetProductModel(&model); + if (R_SUCCEEDED(rc) && model != SetSysProductModel_Invalid) + { + g_productModel = model; + ret = true; + } else { + LOG_MSG_ERROR("setsysGetProductModel failed! (0x%X) (%d).", rc, model); + } + + return ret; +} + static bool _utilsIsDevelopmentUnit(void) { Result rc = 0; @@ -1066,11 +1094,6 @@ static bool _utilsIsDevelopmentUnit(void) return R_SUCCEEDED(rc); } -static bool _utilsAppletModeCheck(void) -{ - return (g_programAppletType > AppletType_Application && g_programAppletType < AppletType_SystemApplication); -} - static bool utilsMountEmmcBisSystemPartitionStorage(void) { Result rc = 0; @@ -1137,7 +1160,7 @@ static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param) static void utilsChangeHomeButtonBlockStatus(bool block) { /* Only change HOME button blocking status if we're running as a regular application or a system application. */ - if (_utilsAppletModeCheck()) return; + if (utilsIsAppletMode()) return; if (block) { diff --git a/source/core/services.c b/source/core/services.c index 16a7158..7535ac2 100644 --- a/source/core/services.c +++ b/source/core/services.c @@ -46,7 +46,6 @@ static Result servicesGetExosphereApiVersion(u32 *out); static Result servicesNifmUserInitialize(void); static bool servicesClkGetServiceType(void *arg); -static bool servicesSplCryptoCheckAvailability(void *arg); /* Global variables. */ @@ -55,16 +54,15 @@ static ServiceInfo g_serviceInfo[] = { { false, "ns", NULL, &nsInitialize, &nsExit }, { false, "csrng", NULL, &csrngInitialize, &csrngExit }, { false, "spl:", NULL, &splInitialize, &splExit }, - { false, "spl:mig", &servicesSplCryptoCheckAvailability, &splCryptoInitialize, &splCryptoExit }, /* Checks if spl:mig is really available (e.g. avoid calling splInitialize twice). */ { false, "pm:dmnt", NULL, &pmdmntInitialize, &pmdmntExit }, { false, "psm", NULL, &psmInitialize, &psmExit }, { false, "nifm:u", NULL, &servicesNifmUserInitialize, &nifmExit }, - { false, "clk", &servicesClkGetServiceType, NULL, NULL }, /* Placeholder for pcv / clkrst. */ + { false, "clk", &servicesClkGetServiceType, NULL, NULL }, /* Placeholder for pcv / clkrst. */ { false, "es", NULL, &esInitialize, &esExit }, { false, "set", NULL, &setInitialize, &setExit }, { false, "set:sys", NULL, &setsysInitialize, &setsysExit }, { false, "set:cal", NULL, &setcalInitialize, &setcalExit }, - { false, "bsd:u", NULL, &socketInitializeDefault, &socketExit } /* socketInitialize*() functions take care of initializing bsd:* too. */ + { false, "bsd:u", NULL, &socketInitializeDefault, &socketExit } /* socketInitialize*() functions take care of initializing bsd:* too. */ }; static const u32 g_serviceInfoCount = MAX_ELEMENTS(g_serviceInfo); @@ -331,14 +329,3 @@ static bool servicesClkGetServiceType(void *arg) return true; } - -static bool servicesSplCryptoCheckAvailability(void *arg) -{ - if (!arg) return false; - - ServiceInfo *info = (ServiceInfo*)arg; - if (strcmp(info->name, "spl:mig") != 0 || info->init_func == NULL || info->close_func == NULL) return false; - - /* Check if spl:mig is available (sysver equal to or greater than 4.0.0). */ - return hosversionAtLeast(4, 0, 0); -} diff --git a/source/core/sha3.c b/source/core/sha3.c index ea71f49..bd2fad0 100644 --- a/source/core/sha3.c +++ b/source/core/sha3.c @@ -1,7 +1,7 @@ /* * sha3.c * - * Copyright (c) Atmosphère-NX + * Copyright (c) Atmosphère-NX. * Copyright (c) 2022, DarkMatterCore . * * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). diff --git a/source/core/tik.c b/source/core/tik.c index 9072bc8..3dae592 100644 --- a/source/core/tik.c +++ b/source/core/tik.c @@ -387,7 +387,6 @@ static bool tikGetDecryptedTitleKey(void *dst, const void *src, u8 key_generatio } const u8 *ticket_common_key = NULL; - Aes128Context titlekey_aes_ctx = {0}; ticket_common_key = keysGetTicketCommonKey(key_generation); if (!ticket_common_key) @@ -396,8 +395,7 @@ static bool tikGetDecryptedTitleKey(void *dst, const void *src, u8 key_generatio return false; } - aes128ContextCreate(&titlekey_aes_ctx, ticket_common_key, false); - aes128DecryptBlock(&titlekey_aes_ctx, dst, src); + aes128EcbCrypt(dst, src, ticket_common_key, false); return true; } diff --git a/source/main.cpp b/source/main.cpp index 143d268..6fc684f 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -48,7 +48,7 @@ int main(int argc, char *argv[]) try { /* Check if we're running under applet mode. */ - if (utilsAppletModeCheck()) + if (utilsIsAppletMode()) { /* Push crash frame with the applet mode warning. */ brls::Application::pushView(new brls::CrashFrame("generic/applet_mode_warning"_i18n, [](brls::View *view) { diff --git a/source/root_view.cpp b/source/root_view.cpp index 7135249..5cdfcd7 100644 --- a/source/root_view.cpp +++ b/source/root_view.cpp @@ -40,7 +40,7 @@ namespace nxdt::views this->setIcon(BOREALIS_ASSET("icon/" APP_TITLE ".jpg")); /* Check if we're running under applet mode. */ - this->applet_mode = utilsAppletModeCheck(); + this->applet_mode = utilsIsAppletMode(); /* Create labels. */ this->applet_mode_lbl = new brls::Label(brls::LabelStyle::HINT, "root_view/applet_mode"_i18n);