From f9bbae81aa60f0d7bd0288fd22b4ab0618e2da96 Mon Sep 17 00:00:00 2001 From: Steveice10 <1269164+Steveice10@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:35:30 -0800 Subject: [PATCH] hw/aes: Clean up key generator. (#7143) --- src/core/hw/aes/key.cpp | 382 ++++++++-------------------------------- src/core/hw/aes/key.h | 1 - 2 files changed, 71 insertions(+), 312 deletions(-) diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index 6614d29a7..42fa562eb 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -3,21 +3,14 @@ // Refer to the license.txt file included. #include -#include -#include #include #include #include #include -#include -#include -#include -#include #include "common/common_paths.h" #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" -#include "core/file_sys/archive_ncch.h" #include "core/hle/service/fs/archive.h" #include "core/hw/aes/arithmetic128.h" #include "core/hw/aes/key.h" @@ -31,18 +24,11 @@ namespace { // normal key dumped from a Wii U solving the equation: // NormalKey = (((KeyX ROL 2) XOR KeyY) + constant) ROL 87 // On a real 3DS the generation for the normal key is hardware based, and thus the constant can't -// get dumped. Generated normal keys are also not accesible on a 3DS. The used formula for +// get dumped. Generated normal keys are also not accessible on a 3DS. The used formula for // calculating the constant is a software implementation of what the hardware generator does. constexpr AESKey generator_constant = {{0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, 0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A}}; -struct KeyDesc { - char key_type; - std::size_t slot_id; - // This key is identical to the key with the same key_type and slot_id -1 - bool same_as_before; -}; - AESKey HexToKey(const std::string& hex) { if (hex.size() < 32) { throw std::invalid_argument("hex string is too short"); @@ -50,7 +36,7 @@ AESKey HexToKey(const std::string& hex) { AESKey key; for (std::size_t i = 0; i < key.size(); ++i) { - key[i] = static_cast(std::stoi(hex.substr(i * 2, 2), 0, 16)); + key[i] = static_cast(std::stoi(hex.substr(i * 2, 2), nullptr, 16)); } return key; @@ -65,6 +51,40 @@ std::vector HexToVector(const std::string& hex) { return vector; } +std::optional ParseCommonKeyName(const std::string& full_name) { + std::size_t index; + int end; + if (std::sscanf(full_name.c_str(), "common%zd%n", &index, &end) == 1 && + end == static_cast(full_name.size())) { + return index; + } else { + return std::nullopt; + } +} + +std::optional> ParseNfcSecretName( + const std::string& full_name) { + std::size_t index; + int end; + if (std::sscanf(full_name.c_str(), "nfcSecret%zd%n", &index, &end) == 1) { + return std::make_pair(index, full_name.substr(end)); + } else { + return std::nullopt; + } +} + +std::optional> ParseKeySlotName(const std::string& full_name) { + std::size_t slot; + char type; + int end; + if (std::sscanf(full_name.c_str(), "slot0x%zXKey%c%n", &slot, &type, &end) == 2 && + end == static_cast(full_name.size())) { + return std::make_pair(slot, type); + } else { + return std::nullopt; + } +} + struct KeySlot { std::optional x; std::optional y; @@ -88,7 +108,7 @@ struct KeySlot { if (x && y) { normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), generator_constant), 87); } else { - normal = {}; + normal.reset(); } } @@ -105,48 +125,13 @@ std::array, NumDlpNfcKeyYs> dlp_nfc_key_y_slots; std::array nfc_secrets; AESIV nfc_iv; -enum class FirmwareType : u32 { - ARM9 = 0, // uses NDMA - ARM11 = 1, // uses XDMA +struct KeyDesc { + char key_type; + std::size_t slot_id; + // This key is identical to the key with the same key_type and slot_id -1 + bool same_as_before; }; -struct FirmwareSectionHeader { - u32_le offset; - u32_le phys_address; - u32_le size; - enum_le firmware_type; - std::array hash; // SHA-256 hash -}; - -struct FIRM_Header { - u32_le magic; // FIRM - u32_le boot_priority; // Usually 0 - u32_le arm11_entrypoint; - u32_le arm9_entrypoint; - INSERT_PADDING_BYTES(0x30); // Reserved - std::array section_headers; // 1st ARM11?, 3rd ARM9 - std::array signature; // RSA-2048 signature of the FIRM header's hash -}; - -struct ARM9_Header { - AESKey enc_key_x; - AESKey key_y; - AESKey CTR; - std::array size; // in ASCII - INSERT_PADDING_BYTES(8); // Unknown - std::array control_block; - std::array hardware_debug_info; - std::array enc_key_x_slot_16; -}; - -std::string KeyToString(AESKey& key) { - std::string s; - for (auto pos : key) { - s += fmt::format("{:02X}", pos); - } - return s; -} - void LoadBootromKeys() { constexpr std::array keys = { {{'X', 0x2C, false}, {'X', 0x2D, true}, {'X', 0x2E, true}, {'X', 0x2F, true}, @@ -199,8 +184,7 @@ void LoadBootromKeys() { } } - LOG_DEBUG(HW_AES, "Loaded Slot{:#02x} Key{}: {}", key.slot_id, key.key_type, - KeyToString(new_key)); + LOG_DEBUG(HW_AES, "Loaded Slot{:#02x} Key{} from Bootrom9.", key.slot_id, key.key_type); switch (key.key_type) { case 'X': @@ -219,227 +203,6 @@ void LoadBootromKeys() { } } -void LoadNativeFirmKeysOld3DS() { - constexpr u64 native_firm_id = 0x00040138'00000002; - FileSys::NCCHArchive archive(native_firm_id, Service::FS::MediaType::NAND); - std::array exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0}; - FileSys::Path file_path = FileSys::MakeNCCHFilePath( - FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::ExeFS, exefs_filepath); - FileSys::Mode open_mode = {}; - open_mode.read_flag.Assign(1); - auto file_result = archive.OpenFile(file_path, open_mode); - if (file_result.Failed()) - return; - - auto firm = std::move(file_result).Unwrap(); - const std::size_t size = firm->GetSize(); - if (size != 966656) { - LOG_ERROR(HW_AES, "native firm has wrong size {}", size); - return; - } - - const auto rsa = RSA::GetSlot(0); - if (!rsa) { - LOG_ERROR(HW_AES, "RSA slot is missing"); - return; - } - - std::vector firm_buffer(size); - firm->Read(0, firm_buffer.size(), firm_buffer.data()); - firm->Close(); - - constexpr std::size_t SLOT_0x25_KEY_X_SECRET_OFFSET = 934444; - constexpr std::size_t SLOT_0x25_KEY_X_SECRET_SIZE = 64; - std::vector secret_data(SLOT_0x25_KEY_X_SECRET_SIZE); - std::memcpy(secret_data.data(), firm_buffer.data() + SLOT_0x25_KEY_X_SECRET_OFFSET, - secret_data.size()); - - auto asn1 = RSA::CreateASN1Message(secret_data); - auto result = rsa.GetSignature(asn1); - if (result.size() < 0x100) { - std::vector temp(0x100); - std::copy(result.begin(), result.end(), temp.end() - result.size()); - result = temp; - } else if (result.size() > 0x100) { - result.resize(0x100); - } - - CryptoPP::SHA256 sha; - std::array hash_result; - sha.CalculateDigest(hash_result.data(), result.data(), result.size()); - AESKey key; - std::memcpy(key.data(), hash_result.data(), sizeof(key)); - key_slots.at(0x2F).SetKeyY(key); - std::memcpy(key.data(), hash_result.data() + sizeof(key), sizeof(key)); - key_slots.at(0x25).SetKeyX(key); -} - -void LoadSafeModeNativeFirmKeysOld3DS() { - // Use the safe mode native firm instead of the normal mode since there are only 2 version of it - // and thus we can use fixed offsets - - constexpr u64 safe_mode_native_firm_id = 0x00040138'00000003; - - FileSys::NCCHArchive archive(safe_mode_native_firm_id, Service::FS::MediaType::NAND); - std::array exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0}; - FileSys::Path file_path = FileSys::MakeNCCHFilePath( - FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::ExeFS, exefs_filepath); - FileSys::Mode open_mode = {}; - open_mode.read_flag.Assign(1); - auto file_result = archive.OpenFile(file_path, open_mode); - if (file_result.Failed()) - return; - - auto firm = std::move(file_result).Unwrap(); - const std::size_t size = firm->GetSize(); - if (size != 843776) { - LOG_ERROR(HW_AES, "safe mode native firm has wrong size {}", size); - return; - } - std::vector firm_buffer(size); - firm->Read(0, firm_buffer.size(), firm_buffer.data()); - firm->Close(); - { - AESKey key; - constexpr std::size_t SLOT_0x31_KEY_Y_OFFSET = 817672; - std::memcpy(key.data(), firm_buffer.data() + SLOT_0x31_KEY_Y_OFFSET, sizeof(key)); - key_slots.at(0x31).SetKeyY(key); - LOG_DEBUG(HW_AES, "Loaded Slot0x31 KeyY: {}", KeyToString(key)); - } - auto LoadCommonKey = [&firm_buffer](std::size_t key_slot) -> AESKey { - constexpr std::size_t START_OFFSET = 836533; - constexpr std::size_t OFFSET = 0x14; // 0x10 bytes for key + 4 bytes between keys - AESKey key; - std::memcpy(key.data(), firm_buffer.data() + START_OFFSET + OFFSET * key_slot, sizeof(key)); - return key; - }; - - for (std::size_t key_slot{0}; key_slot < 6; ++key_slot) { - AESKey key = LoadCommonKey(key_slot); - common_key_y_slots[key_slot] = key; - LOG_DEBUG(HW_AES, "Loaded common key{}: {}", key_slot, KeyToString(key)); - } -} - -void LoadNativeFirmKeysNew3DS() { - // The first 0x10 bytes of the secret_sector are used as a key to decrypt a KeyX from the - // native_firm - const std::string filepath = - FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SECRET_SECTOR; - auto secret = FileUtil::IOFile(filepath, "rb"); - if (!secret) { - return; - } - ASSERT(secret.GetSize() > 0x10); - - AESKey secret_key; - secret.ReadArray(secret_key.data(), secret_key.size()); - - // Use the safe mode native firm instead of the normal mode since there are only 1 version of it - // and thus we can use fixed offsets - constexpr u64 safe_mode_native_firm_id = 0x00040138'20000003; - - // TODO(B3N30): Add the 0x25 KeyX that gets initalized by native_firm - - // TODO(B3N30): Add the 0x18 - 0x1F KeyX that gets initalized by native_firm. This probably - // requires the normal native firm with version > 9.6.0-X - - FileSys::NCCHArchive archive(safe_mode_native_firm_id, Service::FS::MediaType::NAND); - std::array exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0}; - FileSys::Path file_path = FileSys::MakeNCCHFilePath( - FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::ExeFS, exefs_filepath); - FileSys::Mode open_mode = {}; - open_mode.read_flag.Assign(1); - auto file_result = archive.OpenFile(file_path, open_mode); - if (file_result.Failed()) - return; - - auto firm = std::move(file_result).Unwrap(); - std::vector firm_buffer(firm->GetSize()); - firm->Read(0, firm_buffer.size(), firm_buffer.data()); - firm->Close(); - - FIRM_Header header; - std::memcpy(&header, firm_buffer.data(), sizeof(header)); - - auto MakeMagic = [](char a, char b, char c, char d) -> u32 { - return a | b << 8 | c << 16 | d << 24; - }; - if (MakeMagic('F', 'I', 'R', 'M') != header.magic) { - LOG_ERROR(HW_AES, "N3DS SAFE MODE Native Firm has wrong header {}", header.magic); - return; - } - - u32 arm9_offset(0); - u32 arm9_size(0); - for (auto section_header : header.section_headers) { - if (section_header.firmware_type == FirmwareType::ARM9) { - arm9_offset = section_header.offset; - arm9_size = section_header.size; - break; - } - } - - if (arm9_offset != 0x66800) { - LOG_ERROR(HW_AES, "ARM9 binary at wrong offset: {}", arm9_offset); - return; - } - if (arm9_size != 0x8BA00) { - LOG_ERROR(HW_AES, "ARM9 binary has wrong size: {}", arm9_size); - return; - } - - ARM9_Header arm9_header; - std::memcpy(&arm9_header, firm_buffer.data() + arm9_offset, sizeof(arm9_header)); - - AESKey keyX_slot0x15; - CryptoPP::ECB_Mode::Decryption d; - d.SetKey(secret_key.data(), secret_key.size()); - d.ProcessData(keyX_slot0x15.data(), arm9_header.enc_key_x.data(), arm9_header.enc_key_x.size()); - - key_slots.at(0x15).SetKeyX(keyX_slot0x15); - key_slots.at(0x15).SetKeyY(arm9_header.key_y); - auto normal_key_slot0x15 = key_slots.at(0x15).normal; - if (!normal_key_slot0x15) { - LOG_ERROR(HW_AES, "Failed to get normal key for slot id 0x15"); - return; - } - - constexpr u32 ARM9_BINARY_OFFSET = 0x800; // From the beginning of the ARM9 section - std::vector enc_arm9_binary; - enc_arm9_binary.resize(arm9_size - ARM9_BINARY_OFFSET); - ASSERT(enc_arm9_binary.size() + arm9_offset + ARM9_BINARY_OFFSET < firm_buffer.size()); - std::memcpy(enc_arm9_binary.data(), firm_buffer.data() + arm9_offset + ARM9_BINARY_OFFSET, - enc_arm9_binary.size()); - - std::vector arm9_binary; - arm9_binary.resize(enc_arm9_binary.size()); - CryptoPP::CTR_Mode::Decryption d2; - d2.SetKeyWithIV(normal_key_slot0x15->data(), normal_key_slot0x15->size(), - arm9_header.CTR.data(), arm9_header.CTR.size()); - d2.ProcessData(arm9_binary.data(), enc_arm9_binary.data(), enc_arm9_binary.size()); - { - AESKey key; - constexpr std::size_t SLOT_0x31_KEY_Y_OFFSET = 517368; - std::memcpy(key.data(), arm9_binary.data() + SLOT_0x31_KEY_Y_OFFSET, sizeof(key)); - key_slots.at(0x31).SetKeyY(key); - LOG_DEBUG(HW_AES, "Loaded Slot0x31 KeyY: {}", KeyToString(key)); - } - auto LoadCommonKey = [&arm9_binary](std::size_t key_slot) -> AESKey { - constexpr std::size_t START_OFFSET = 541065; - constexpr std::size_t OFFSET = 0x14; // 0x10 bytes for key + 4 bytes between keys - AESKey key; - std::memcpy(key.data(), arm9_binary.data() + START_OFFSET + OFFSET * key_slot, sizeof(key)); - return key; - }; - - for (std::size_t key_slot{0}; key_slot < 6; ++key_slot) { - AESKey key = LoadCommonKey(key_slot); - common_key_y_slots[key_slot] = key; - LOG_DEBUG(HW_AES, "Loaded common key{}: {}", key_slot, KeyToString(key)); - } -} - void LoadPresetKeys() { const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + AES_KEYS; FileUtil::CreateFullPath(filepath); // Create path if not already created @@ -467,19 +230,19 @@ void LoadPresetKeys() { const std::string& name = parts[0]; - std::size_t nfc_secret_index; - if (std::sscanf(name.c_str(), "nfcSecret%zd", &nfc_secret_index) == 1) { + const auto nfc_secret = ParseNfcSecretName(name); + if (nfc_secret) { auto value = HexToVector(parts[1]); - if (nfc_secret_index >= nfc_secrets.size()) { - LOG_ERROR(HW_AES, "Invalid NFC secret index {}", nfc_secret_index); - } else if (name.ends_with("Phrase")) { - nfc_secrets[nfc_secret_index].phrase = value; - } else if (name.ends_with("Seed")) { - nfc_secrets[nfc_secret_index].seed = value; - } else if (name.ends_with("HmacKey")) { - nfc_secrets[nfc_secret_index].hmac_key = value; + if (nfc_secret->first >= nfc_secrets.size()) { + LOG_ERROR(HW_AES, "Invalid NFC secret index {}", nfc_secret->first); + } else if (nfc_secret->second == "Phrase") { + nfc_secrets[nfc_secret->first].phrase = value; + } else if (nfc_secret->second == "Seed") { + nfc_secrets[nfc_secret->first].seed = value; + } else if (nfc_secret->second == "HmacKey") { + nfc_secrets[nfc_secret->first].hmac_key = value; } else { - LOG_ERROR(HW_AES, "Invalid NFC secret {}", name); + LOG_ERROR(HW_AES, "Invalid NFC secret '{}'", name); } continue; } @@ -492,12 +255,12 @@ void LoadPresetKeys() { continue; } - std::size_t common_key_index; - if (std::sscanf(name.c_str(), "common%zd", &common_key_index) == 1) { - if (common_key_index >= common_key_y_slots.size()) { - LOG_ERROR(HW_AES, "Invalid common key index {}", common_key_index); + const auto common_key = ParseCommonKeyName(name); + if (common_key) { + if (common_key >= common_key_y_slots.size()) { + LOG_ERROR(HW_AES, "Invalid common key index {}", common_key.value()); } else { - common_key_y_slots[common_key_index] = key; + common_key_y_slots[common_key.value()] = key; } continue; } @@ -517,30 +280,29 @@ void LoadPresetKeys() { continue; } - std::size_t slot_id; - char key_type; - if (std::sscanf(name.c_str(), "slot0x%zXKey%c", &slot_id, &key_type) != 2) { - LOG_ERROR(HW_AES, "Invalid key name {}", name); + const auto key_slot = ParseKeySlotName(name); + if (!key_slot) { + LOG_ERROR(HW_AES, "Invalid key name '{}'", name); continue; } - if (slot_id >= MaxKeySlotID) { - LOG_ERROR(HW_AES, "Out of range slot ID {:#X}", slot_id); + if (key_slot->first >= MaxKeySlotID) { + LOG_ERROR(HW_AES, "Out of range key slot ID {:#X}", key_slot->first); continue; } - switch (key_type) { + switch (key_slot->second) { case 'X': - key_slots.at(slot_id).SetKeyX(key); + key_slots.at(key_slot->first).SetKeyX(key); break; case 'Y': - key_slots.at(slot_id).SetKeyY(key); + key_slots.at(key_slot->first).SetKeyY(key); break; case 'N': - key_slots.at(slot_id).SetNormalKey(key); + key_slots.at(key_slot->first).SetNormalKey(key); break; default: - LOG_ERROR(HW_AES, "Invalid key type {}", key_type); + LOG_ERROR(HW_AES, "Invalid key type '{}'", key_slot->second); break; } } @@ -550,14 +312,12 @@ void LoadPresetKeys() { void InitKeys(bool force) { static bool initialized = false; - if (initialized && !force) + if (initialized && !force) { return; + } initialized = true; HW::RSA::InitSlots(); LoadBootromKeys(); - LoadNativeFirmKeysOld3DS(); - LoadSafeModeNativeFirmKeysOld3DS(); - LoadNativeFirmKeysNew3DS(); LoadPresetKeys(); } diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h index b12e250bf..584ab284e 100644 --- a/src/core/hw/aes/key.h +++ b/src/core/hw/aes/key.h @@ -76,7 +76,6 @@ using AESIV = std::array; void InitKeys(bool force = false); -void SetGeneratorConstant(const AESKey& key); void SetKeyX(std::size_t slot_id, const AESKey& key); void SetKeyY(std::size_t slot_id, const AESKey& key); void SetNormalKey(std::size_t slot_id, const AESKey& key);