From ebeea43fb806095bf0cf98b70424166d9e9eb861 Mon Sep 17 00:00:00 2001
From: B3n30 <benediktthomas@gmail.com>
Date: Wed, 26 Feb 2020 16:43:53 +0100
Subject: [PATCH] Load RSA slot_0 data from bootrom; load and decrypt data from
 native firm for NCCHSecure2 keyslot

---
 src/core/CMakeLists.txt |   2 +
 src/core/hw/aes/key.cpp |  68 +++++++++++++++++++++++++--
 src/core/hw/rsa/rsa.cpp | 102 ++++++++++++++++++++++++++++++++++++++++
 src/core/hw/rsa/rsa.h   |  36 ++++++++++++++
 4 files changed, 204 insertions(+), 4 deletions(-)
 create mode 100644 src/core/hw/rsa/rsa.cpp
 create mode 100644 src/core/hw/rsa/rsa.h

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index ab9f52e32..8b7271fee 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -421,6 +421,8 @@ add_library(core STATIC
     hw/hw.h
     hw/lcd.cpp
     hw/lcd.h
+    hw/rsa/rsa.cpp
+    hw/rsa/rsa.h
     hw/y2r.cpp
     hw/y2r.h
     loader/3dsx.cpp
diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp
index 37a547e5c..c61b04a2e 100644
--- a/src/core/hw/aes/key.cpp
+++ b/src/core/hw/aes/key.cpp
@@ -8,6 +8,7 @@
 #include <sstream>
 #include <cryptopp/aes.h>
 #include <cryptopp/modes.h>
+#include <cryptopp/sha.h>
 #include <fmt/format.h>
 #include "common/common_paths.h"
 #include "common/file_util.h"
@@ -17,6 +18,7 @@
 #include "core/hle/service/fs/archive.h"
 #include "core/hw/aes/arithmetic128.h"
 #include "core/hw/aes/key.h"
+#include "core/hw/rsa/rsa.h"
 
 namespace HW::AES {
 
@@ -26,7 +28,7 @@ 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 accesible 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}};
@@ -203,13 +205,69 @@ void LoadBootromKeys() {
 }
 
 void LoadNativeFirmKeysOld3DS() {
+    constexpr u64 native_firm_id = 0x00040138'00000002;
+    FileSys::NCCHArchive archive(native_firm_id, Service::FS::MediaType::NAND);
+    std::array<char, 8> 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;
+    }
+
+    auto rsa = RSA::GetSlot(0);
+    if (!rsa) {
+        LOG_ERROR(HW_AES, "RSA slot is missing");
+        return;
+    }
+
+    std::vector<u8> 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<u8> 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<u8> temp;
+        temp.resize(0x100 - result.size());
+        std::fill(temp.begin(), temp.end(), 0);
+        std::copy(result.begin(), result.end(), std::back_inserter(temp));
+        result = temp;
+    } else if (result.size() > 0x100) {
+        std::vector<u8> temp(result.begin(), result.begin() + 0x100);
+        result = temp;
+    }
+
+    CryptoPP::SHA256 sha;
+    std::array<u8, CryptoPP::SHA256::DIGESTSIZE> 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 LoadSaveModeNativeFirmKeysOld3DS() {
     // Use the save 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 save_mode_native_firm_id = 0x00040138'00000003;
 
-    // TODO(B3N30): Add the 0x25 KeyX that gets initalized by native_firm
-
     FileSys::NCCHArchive archive(save_mode_native_firm_id, Service::FS::MediaType::NAND);
     std::array<char, 8> exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0};
     FileSys::Path file_path = FileSys::MakeNCCHFilePath(
@@ -443,11 +501,13 @@ void InitKeys() {
     static bool initialized = false;
     if (initialized)
         return;
+    initialized = true;
+    HW::RSA::InitSlots();
     LoadBootromKeys();
     LoadNativeFirmKeysOld3DS();
+    LoadSaveModeNativeFirmKeysOld3DS();
     LoadNativeFirmKeysNew3DS();
     LoadPresetKeys();
-    initialized = true;
 }
 
 void SetKeyX(std::size_t slot_id, const AESKey& key) {
diff --git a/src/core/hw/rsa/rsa.cpp b/src/core/hw/rsa/rsa.cpp
new file mode 100644
index 000000000..c436ad74f
--- /dev/null
+++ b/src/core/hw/rsa/rsa.cpp
@@ -0,0 +1,102 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <sstream>
+#include <cryptopp/integer.h>
+#include <cryptopp/nbtheory.h>
+#include <cryptopp/sha.h>
+#include <fmt/format.h>
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/hw/rsa/rsa.h"
+
+namespace HW::RSA {
+
+namespace {
+std::vector<u8> HexToBytes(const std::string& hex) {
+    std::vector<u8> bytes;
+
+    for (unsigned int i = 0; i < hex.length(); i += 2) {
+        std::string byteString = hex.substr(i, 2);
+        u8 byte = (u8)strtol(byteString.c_str(), NULL, 16);
+        bytes.push_back(byte);
+    }
+    return bytes;
+};
+} // namespace
+
+constexpr std::size_t SlotSize = 4;
+std::array<RsaSlot, SlotSize> rsa_slots;
+
+std::vector<u8> RsaSlot::GetSignature(const std::vector<u8>& message) {
+    CryptoPP::Integer sig =
+        CryptoPP::ModularExponentiation(CryptoPP::Integer(message.data(), message.size()),
+                                        CryptoPP::Integer(exponent.data(), exponent.size()),
+                                        CryptoPP::Integer(modulus.data(), modulus.size()));
+    std::stringstream ss;
+    ss << std::hex << sig;
+    return HexToBytes(ss.str());
+}
+
+void InitSlots() {
+    static bool initialized = false;
+    if (initialized)
+        return;
+    initialized = true;
+
+    const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9;
+    auto file = FileUtil::IOFile(filepath, "rb");
+    if (!file) {
+        return;
+    }
+
+    const std::size_t length = file.GetSize();
+    if (length != 65536) {
+        LOG_ERROR(HW_AES, "Bootrom9 size is wrong: {}", length);
+        return;
+    }
+
+    constexpr std::size_t RSA_MODULUS_POS = 0xB3E0;
+    file.Seek(RSA_MODULUS_POS, SEEK_SET);
+    std::vector<u8> modulus;
+    modulus.resize(256);
+    file.ReadArray(modulus.data(), modulus.size());
+
+    constexpr std::size_t RSA_EXPONENT_POS = 0xB4E0;
+    file.Seek(RSA_EXPONENT_POS, SEEK_SET);
+    std::vector<u8> exponent;
+    exponent.resize(256);
+    file.ReadArray(exponent.data(), exponent.size());
+
+    rsa_slots[0] = RsaSlot(exponent, modulus);
+    // TODO(B3N30): Initalize the other slots. But since they aren't used at all, we can skip them
+    // for now
+}
+
+RsaSlot GetSlot(std::size_t slot_id) {
+    if (slot_id >= rsa_slots.size())
+        return RsaSlot{};
+    return rsa_slots[slot_id];
+}
+
+std::vector<u8> CreateASN1Message(const std::vector<u8>& data) {
+    static constexpr auto asn1_header =
+        "0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+        "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+        "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+        "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+        "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+        "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+        "FFFFFFFFFFFFFFFFFFFFFFFF003031300D060960864801650304020105000420";
+    std::vector<u8> message = HexToBytes(asn1_header);
+    CryptoPP::SHA256 sha;
+    std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash;
+    sha.CalculateDigest(hash.data(), data.data(), data.size());
+    std::copy(hash.begin(), hash.end(), std::back_inserter(message));
+    return message;
+}
+
+} // namespace HW::RSA
\ No newline at end of file
diff --git a/src/core/hw/rsa/rsa.h b/src/core/hw/rsa/rsa.h
new file mode 100644
index 000000000..869d98d89
--- /dev/null
+++ b/src/core/hw/rsa/rsa.h
@@ -0,0 +1,36 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+
+namespace HW::RSA {
+
+class RsaSlot {
+public:
+    RsaSlot() : init(false) {}
+    RsaSlot(const std::vector<u8>& exponent, const std::vector<u8>& modulus)
+        : init(true), exponent(exponent), modulus(modulus) {}
+    std::vector<u8> GetSignature(const std::vector<u8>& message);
+
+    operator bool() const {
+        // TODO(B3N30): Maybe check if exponent and modulus are vailid
+        return init;
+    }
+
+private:
+    bool init;
+    std::vector<u8> exponent;
+    std::vector<u8> modulus;
+};
+
+void InitSlots();
+
+RsaSlot GetSlot(std::size_t slot_id);
+
+std::vector<u8> CreateASN1Message(const std::vector<u8>& data);
+
+} // namespace HW::RSA
\ No newline at end of file