From 2e5bc0cb14d79a4626e09ed2fee7e24fc2d25d87 Mon Sep 17 00:00:00 2001 From: Mary Date: Mon, 19 Jun 2023 23:13:20 +0200 Subject: [PATCH] feat: crypto: Add basic AES keyslot manager We loads keys from AppData/Alber/sysdata/aes_keys.txt. NOTE: We do differ from other emulators by not hardcoding the generator key, it's the user responsibility to provide it in aes_keys.txt. --- CMakeLists.txt | 5 +- include/crypto/aes_engine.hpp | 167 +++++++++++++++++++++++++++++++++ include/emulator.hpp | 2 + include/helpers.hpp | 15 +++ src/core/crypto/aes_engine.cpp | 84 +++++++++++++++++ src/emulator.cpp | 9 +- 6 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 include/crypto/aes_engine.hpp create mode 100644 src/core/crypto/aes_engine.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 74c2d429..4a70e4b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,7 @@ endif() set(SOURCE_FILES src/main.cpp src/emulator.cpp src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp src/core/memory.cpp ) +set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp) set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp src/core/kernel/memory_management.cpp src/core/kernel/ports.cpp src/core/kernel/events.cpp src/core/kernel/threads.cpp @@ -119,6 +120,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/system_models.hpp include/services/dlp_srvr.hpp include/result/result.hpp include/result/result_common.hpp include/result/result_fs.hpp include/result/result_fnd.hpp include/result/result_gsp.hpp include/result/result_kernel.hpp include/result/result_os.hpp + include/crypto/aes_engine.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp @@ -130,6 +132,7 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp source_group("Header Files\\Core" FILES ${HEADER_FILES}) source_group("Source Files\\Core" FILES ${SOURCE_FILES}) +source_group("Source Files\\Core\\Crypto" FILES ${CRYPTO_SOURCE_FILES}) source_group("Source Files\\Core\\Filesystem" FILES ${FS_SOURCE_FILES}) source_group("Source Files\\Core\\Kernel" FILES ${KERNEL_SOURCE_FILES}) source_group("Source Files\\Core\\Loader" FILES ${LOADER_SOURCE_FILES}) @@ -138,7 +141,7 @@ source_group("Source Files\\Core\\PICA" FILES ${PICA_SOURCE_FILES}) source_group("Source Files\\Core\\OpenGL Renderer" FILES ${RENDERER_GL_SOURCE_FILES}) source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES}) -add_executable(Alber ${SOURCE_FILES} ${FS_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} +add_executable(Alber ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${PICA_SOURCE_FILES} ${RENDERER_GL_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) target_link_libraries(Alber PRIVATE dynarmic SDL2-static glad) diff --git a/include/crypto/aes_engine.hpp b/include/crypto/aes_engine.hpp new file mode 100644 index 00000000..19408962 --- /dev/null +++ b/include/crypto/aes_engine.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "helpers.hpp" + +namespace Crypto { + constexpr std::size_t AesKeySize = 0x10; + + using AESKey = std::array; + + template + static std::array rolArray(const std::array& value, std::size_t bits) { + const auto bitWidth = N * UINT8_WIDTH; + + bits %= bitWidth; + + const auto byteShift = bits / UINT8_WIDTH; + const auto bitShift = bits % UINT8_WIDTH; + + std::array result; + + for (std::size_t i = 0; i < N; i++) { + result[i] = ((value[(i + byteShift) % N] << bitShift) | (value[(i + byteShift + 1) % N] >> (UINT8_WIDTH - bitShift))) & UINT8_MAX; + } + + return result; + } + + template + static std::array addArray(const std::array& a, const std::array& b) { + std::array result; + std::size_t sum = 0; + std::size_t carry = 0; + + for (std::int64_t i = N - 1; i >= 0; i--) { + sum = a[i] + b[i] + carry; + carry = sum >> UINT8_WIDTH; + result[i] = static_cast(sum & UINT8_MAX); + } + + return result; + } + + template + static std::array xorArray(const std::array& a, const std::array& b) { + std::array result; + + for (std::size_t i = 0; i < N; i++) { + result[i] = a[i] ^ b[i]; + } + + return result; + } + + static std::optional createKeyFromHex(const std::string& hex) { + if (hex.size() < 32) { + return {}; + } + + AESKey rawKey; + for (std::size_t i = 0; i < rawKey.size(); i++) { + rawKey[i] = static_cast(std::stoi(hex.substr(i * 2, 2), 0, 16)); + } + + return rawKey; + } + + struct AESKeySlot { + std::optional keyX; + std::optional keyY; + std::optional normalKey; + }; + + enum KeySlotId : std::size_t { + NCCHKey0 = 0x2C, + NCCHKey1 = 0x25, + NCCHKey2 = 0x18, + NCCHKey3 = 0x1B, + }; + + class AESEngine { + private: + constexpr static std::size_t AesKeySlotCount = 0x40; + + std::optional m_generator; + std::array m_slots; + + constexpr void updateNormalKey(std::size_t slotId) { + if (m_generator.has_value() && hasKeyX(slotId) && hasKeyY(slotId)) { + auto &keySlot = m_slots.at(slotId); + AESKey keyX = keySlot.keyX.value(); + AESKey keyY = keySlot.keyY.value(); + + keySlot.normalKey = rolArray(addArray(xorArray(rolArray(keyX, 2), keyY), m_generator.value()), 87); + } + } + + public: + AESEngine() {} + + void loadKeys(const std::filesystem::path& path); + + constexpr bool hasKeyX(std::size_t slotId) { + if (slotId >= AesKeySlotCount) { + return false; + } + + return m_slots.at(slotId).keyX.has_value(); + } + + constexpr AESKey getKeyX(std::size_t slotId) { + return m_slots.at(slotId).keyX.value_or(AESKey{}); + } + + constexpr void setKeyX(std::size_t slotId, const AESKey &key) { + if (slotId < AesKeySlotCount) { + m_slots.at(slotId).keyX = key; + + updateNormalKey(slotId); + } + } + + constexpr bool hasKeyY(std::size_t slotId) { + if (slotId >= AesKeySlotCount) { + return false; + } + + return m_slots.at(slotId).keyY.has_value(); + } + + constexpr AESKey getKeyY(std::size_t slotId) { + return m_slots.at(slotId).keyY.value_or(AESKey{}); + } + + constexpr void setKeyY(std::size_t slotId, const AESKey &key) { + if (slotId < AesKeySlotCount) { + m_slots.at(slotId).keyY = key; + + updateNormalKey(slotId); + } + } + + constexpr bool hasNormalKey(std::size_t slotId) { + if (slotId >= AesKeySlotCount) { + return false; + } + + return m_slots.at(slotId).normalKey.has_value(); + } + + constexpr AESKey getNormalKey(std::size_t slotId) { + return m_slots.at(slotId).normalKey.value_or(AESKey{}); + } + + constexpr void setNormalKey(std::size_t slotId, const AESKey &key) { + if (slotId < AesKeySlotCount) { + m_slots.at(slotId).normalKey = key; + } + } + }; +} \ No newline at end of file diff --git a/include/emulator.hpp b/include/emulator.hpp index 46aa0ade..fe9d0466 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -6,6 +6,7 @@ #include #include "cpu.hpp" +#include "crypto/aes_engine.hpp" #include "io_file.hpp" #include "memory.hpp" #include "opengl.hpp" @@ -20,6 +21,7 @@ class Emulator { GPU gpu; Memory memory; Kernel kernel; + Crypto::AESEngine aesEngine; SDL_Window* window; SDL_GLContext glContext; diff --git a/include/helpers.hpp b/include/helpers.hpp index 853e487b..e110a8b6 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -164,6 +166,19 @@ namespace Helpers { return std::bit_cast(from); } #endif + + static std::vector split(const std::string& s, const char c) { + std::istringstream tmp(s); + std::vector result(1); + + while (std::getline(tmp, *result.rbegin(), c)) { + result.emplace_back(); + } + + // Remove temporary slot + result.pop_back(); + return result; + } }; // namespace Helpers // UDLs for memory size values diff --git a/src/core/crypto/aes_engine.cpp b/src/core/crypto/aes_engine.cpp new file mode 100644 index 00000000..53694d02 --- /dev/null +++ b/src/core/crypto/aes_engine.cpp @@ -0,0 +1,84 @@ +#include +#include + +#include "crypto/aes_engine.hpp" +#include "helpers.hpp" + +namespace Crypto { + void AESEngine::loadKeys(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::in); + + if (file.fail()) { + Helpers::warn("keys: Couldn't read key file: %s", path.c_str()); + return; + } + + while (!file.eof()) { + std::string line; + + std::getline(file, line); + + // Skip obvious invalid lines + if (line.empty() || line.starts_with("#")) { + continue; + } + + const auto parts = Helpers::split(line, '='); + + if (parts.size() != 2) { + Helpers::warn("keys: Failed to parse %s", line.c_str()); + continue; + } + + const std::string& name = parts[0]; + const std::string& rawKeyHex = parts[1]; + + std::size_t slotId; + char keyType; + + bool is_generator = name == "generator"; + + if (!is_generator && std::sscanf(name.c_str(), "slot0x%zXKey%c", &slotId, &keyType) != 2) { + Helpers::warn("keys: Ignoring unknown key %s", name.c_str()); + continue; + } + + auto key = createKeyFromHex(rawKeyHex); + + if (!key.has_value()) { + Helpers::warn("keys: Failed to parse raw key %s", rawKeyHex.c_str()); + continue; + } + + if (is_generator) { + m_generator = key; + continue; + } + + if (slotId >= AesKeySlotCount) { + Helpers::warn("keys: Invalid key slot id %u", slotId); + continue; + } + + switch (keyType) { + case 'X': + setKeyX(slotId, key.value()); + break; + case 'Y': + setKeyY(slotId, key.value()); + break; + case 'N': + setNormalKey(slotId, key.value()); + break; + default: + Helpers::warn("keys: Invalid key type %c", keyType); + break; + } + } + + // As the generator key can be set at any time, force update all normal keys. + for (std::size_t i = 0; i < AesKeySlotCount; i++) { + updateNormalKey(i); + } + } +}; \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index 8141a94a..92a9fb30 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -135,8 +135,15 @@ bool Emulator::loadROM(const std::filesystem::path& path) { // Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in // %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart char* appData = SDL_GetPrefPath(nullptr, "Alber"); - const std::filesystem::path dataPath = std::filesystem::path(appData) / path.filename().stem(); + const std::filesystem::path appDataPath = std::filesystem::path(appData); + const std::filesystem::path dataPath = appDataPath / path.filename().stem(); + const std::filesystem::path aesKeysPath = appDataPath / "sysdata" / "aes_keys.txt"; IOFile::setAppDataDir(dataPath); + + if (std::filesystem::exists(aesKeysPath)) { + aesEngine.loadKeys(aesKeysPath); + } + SDL_free(appData); kernel.initializeFS();