diff options
| author | bunnei <bunneidev@gmail.com> | 2018-10-12 22:55:49 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-10-12 22:55:49 -0400 | 
| commit | 1584fb6b385d51ef40e1e22c180322a552d9c98a (patch) | |
| tree | f129dca6721bac37c7cd96657cdcc77401899f1c | |
| parent | c2aa4293ec647a3712299c5b53f3ca592aaf9c0e (diff) | |
| parent | 3ec054643e50f2845fb6a1a924b83bd71a0e2234 (diff) | |
Merge pull request #1409 from DarkLordZach/key-derivation
crypto: Add support for full key derivation
| m--------- | externals/mbedtls | 0 | ||||
| -rw-r--r-- | src/core/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/core/crypto/key_manager.cpp | 799 | ||||
| -rw-r--r-- | src/core/crypto/key_manager.h | 104 | ||||
| -rw-r--r-- | src/core/crypto/partition_data_manager.cpp | 601 | ||||
| -rw-r--r-- | src/core/crypto/partition_data_manager.h | 105 | ||||
| -rw-r--r-- | src/core/file_sys/vfs.h | 10 | ||||
| -rw-r--r-- | src/core/file_sys/vfs_types.h | 21 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 82 | ||||
| -rw-r--r-- | src/yuzu/main.h | 6 | ||||
| -rw-r--r-- | src/yuzu/main.ui | 6 | 
11 files changed, 1663 insertions, 74 deletions
diff --git a/externals/mbedtls b/externals/mbedtls -Subproject d409b75a4cf75a5b358b352c75826ddbca44db5 +Subproject a280e602f3a4ae001d3a83cbc3e6e04c99c2227 diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e4a676e91..4fddaafd1 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -18,6 +18,8 @@ add_library(core STATIC      crypto/encryption_layer.h      crypto/key_manager.cpp      crypto/key_manager.h +    crypto/partition_data_manager.cpp +    crypto/partition_data_manager.h      crypto/ctr_encryption_layer.cpp      crypto/ctr_encryption_layer.h      crypto/xts_encryption_layer.cpp @@ -70,6 +72,7 @@ add_library(core STATIC      file_sys/vfs_real.cpp      file_sys/vfs_real.h      file_sys/vfs_static.h +    file_sys/vfs_types.h      file_sys/vfs_vector.cpp      file_sys/vfs_vector.h      file_sys/xts_archive.cpp diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index bf3a70944..a59a7e1f5 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -4,23 +4,56 @@  #include <algorithm>  #include <array> +#include <bitset> +#include <cctype>  #include <fstream>  #include <locale> +#include <map>  #include <sstream>  #include <string_view>  #include <tuple>  #include <vector> +#include <mbedtls/bignum.h> +#include <mbedtls/cipher.h> +#include <mbedtls/cmac.h> +#include <mbedtls/sha256.h> +#include "common/common_funcs.h"  #include "common/common_paths.h"  #include "common/file_util.h"  #include "common/hex_util.h"  #include "common/logging/log.h"  #include "core/crypto/aes_util.h"  #include "core/crypto/key_manager.h" +#include "core/crypto/partition_data_manager.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/registered_cache.h" +#include "core/hle/service/filesystem/filesystem.h"  #include "core/loader/loader.h"  #include "core/settings.h"  namespace Core::Crypto { +constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; + +using namespace Common; + +const std::array<SHA256Hash, 2> eticket_source_hashes{ +    "B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source +    "E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source +}; + +const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{ +    {{S128KeyType::Master, 0}, "master_key_"}, +    {{S128KeyType::Package1, 0}, "package1_key_"}, +    {{S128KeyType::Package2, 0}, "package2_key_"}, +    {{S128KeyType::Titlekek, 0}, "titlekek_"}, +    {{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"}, +    {{S128KeyType::Keyblob, 0}, "keyblob_key_"}, +    {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, +}; +  Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {      Key128 out{}; @@ -37,6 +70,77 @@ Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, K      return out;  } +Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source) { +    AESCipher<Key128> sbk_cipher(sbk, Mode::ECB); +    AESCipher<Key128> tsec_cipher(tsec, Mode::ECB); +    tsec_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt); +    sbk_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt); +    return source; +} + +Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source) { +    Key128 master_root; +    std::memcpy(master_root.data(), keyblob.data(), sizeof(Key128)); + +    AESCipher<Key128> master_cipher(master_root, Mode::ECB); + +    Key128 master{}; +    master_cipher.Transcode(master_source.data(), master_source.size(), master.data(), Op::Decrypt); +    return master; +} + +std::array<u8, 144> DecryptKeyblob(const std::array<u8, 176>& encrypted_keyblob, +                                   const Key128& key) { +    std::array<u8, 0x90> keyblob; +    AESCipher<Key128> cipher(key, Mode::CTR); +    cipher.SetIV(std::vector<u8>(encrypted_keyblob.data() + 0x10, encrypted_keyblob.data() + 0x20)); +    cipher.Transcode(encrypted_keyblob.data() + 0x20, keyblob.size(), keyblob.data(), Op::Decrypt); +    return keyblob; +} + +void KeyManager::DeriveGeneralPurposeKeys(u8 crypto_revision) { +    const auto kek_generation_source = +        GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)); +    const auto key_generation_source = +        GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)); + +    if (HasKey(S128KeyType::Master, crypto_revision)) { +        for (auto kak_type : +             {KeyAreaKeyType::Application, KeyAreaKeyType::Ocean, KeyAreaKeyType::System}) { +            if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), +                       static_cast<u64>(kak_type))) { +                const auto source = +                    GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), +                           static_cast<u64>(kak_type)); +                const auto kek = +                    GenerateKeyEncryptionKey(source, GetKey(S128KeyType::Master, crypto_revision), +                                             kek_generation_source, key_generation_source); +                SetKey(S128KeyType::KeyArea, kek, crypto_revision, static_cast<u64>(kak_type)); +            } +        } + +        AESCipher<Key128> master_cipher(GetKey(S128KeyType::Master, crypto_revision), Mode::ECB); +        for (auto key_type : {SourceKeyType::Titlekek, SourceKeyType::Package2}) { +            if (HasKey(S128KeyType::Source, static_cast<u64>(key_type))) { +                Key128 key{}; +                master_cipher.Transcode( +                    GetKey(S128KeyType::Source, static_cast<u64>(key_type)).data(), key.size(), +                    key.data(), Op::Decrypt); +                SetKey(key_type == SourceKeyType::Titlekek ? S128KeyType::Titlekek +                                                           : S128KeyType::Package2, +                       key, crypto_revision); +            } +        } +    } +} + +Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { +    AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB); +    Key128 mac_key{}; +    mac_cipher.Transcode(mac_source.data(), mac_key.size(), mac_key.data(), Op::Decrypt); +    return mac_key; +} +  boost::optional<Key128> DeriveSDSeed() {      const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +                                         "/system/save/8000000000000043", @@ -71,23 +175,24 @@ boost::optional<Key128> DeriveSDSeed() {      return seed;  } -Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys) { -    if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK))) +Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) { +    if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)))          return Loader::ResultStatus::ErrorMissingSDKEKSource; -    if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration))) +    if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)))          return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource;      if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)))          return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource;      const auto sd_kek_source = -        keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK)); +        keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek));      const auto aes_kek_gen = -        keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration)); +        keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));      const auto aes_key_gen =          keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));      const auto master_00 = keys.GetKey(S128KeyType::Master);      const auto sd_kek =          GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen); +    keys.SetKey(S128KeyType::SDKek, sd_kek);      if (!keys.HasKey(S128KeyType::SDSeed))          return Loader::ResultStatus::ErrorMissingSDSeed; @@ -118,9 +223,141 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManag                         return source; ///< Return unaltered source to satisfy output requirement.                     }); +    keys.SetKey(S256KeyType::SDKey, sd_keys[0], static_cast<u64>(SDKeyType::Save)); +    keys.SetKey(S256KeyType::SDKey, sd_keys[1], static_cast<u64>(SDKeyType::NCA)); +      return Loader::ResultStatus::Success;  } +std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { +    if (!ticket_save.IsOpen()) +        return {}; + +    std::vector<u8> buffer(ticket_save.GetSize()); +    ticket_save.ReadBytes(buffer.data(), buffer.size()); + +    std::vector<TicketRaw> out; +    u32 magic{}; +    for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { +        if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && +            buffer[offset + 3] == 0x0) { +            out.emplace_back(); +            auto& next = out.back(); +            std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw)); +            offset += next.size(); +        } +    } + +    return out; +} + +template <size_t size> +static std::array<u8, size> operator^(const std::array<u8, size>& lhs, +                                      const std::array<u8, size>& rhs) { +    std::array<u8, size> out{}; +    std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(), std::bit_xor<>()); +    return out; +} + +template <size_t target_size, size_t in_size> +static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) { +    std::array<u8, in_size + 4> seed_exp{}; +    std::memcpy(seed_exp.data(), seed.data(), in_size); + +    std::vector<u8> out; +    size_t i = 0; +    while (out.size() < target_size) { +        out.resize(out.size() + 0x20); +        seed_exp[in_size + 3] = i; +        mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0); +        ++i; +    } + +    std::array<u8, target_size> target; +    std::memcpy(target.data(), out.data(), target_size); +    return target; +} + +template <size_t size> +static boost::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { +    u64 offset = 0; +    for (size_t i = 0x20; i < data.size() - 0x10; ++i) { +        if (data[i] == 0x1) { +            offset = i + 1; +            break; +        } else if (data[i] != 0x0) { +            return boost::none; +        } +    } + +    return offset; +} + +boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, +                                                       const RSAKeyPair<2048>& key) { +    u32 cert_authority; +    std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority)); +    if (cert_authority == 0) +        return boost::none; +    if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) +        LOG_INFO(Crypto, +                 "Attempting to parse ticket with non-standard certificate authority {:08X}.", +                 cert_authority); + +    Key128 rights_id; +    std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128)); + +    if (rights_id == Key128{}) +        return boost::none; + +    Key128 key_temp{}; + +    if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) { +        std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size()); +        return std::make_pair(rights_id, key_temp); +    } + +    mbedtls_mpi D; // RSA Private Exponent +    mbedtls_mpi N; // RSA Modulus +    mbedtls_mpi S; // Input +    mbedtls_mpi M; // Output + +    mbedtls_mpi_init(&D); +    mbedtls_mpi_init(&N); +    mbedtls_mpi_init(&S); +    mbedtls_mpi_init(&M); + +    mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); +    mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); +    mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100); + +    mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); + +    std::array<u8, 0x100> rsa_step; +    mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size()); + +    u8 m_0 = rsa_step[0]; +    std::array<u8, 0x20> m_1; +    std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size()); +    std::array<u8, 0xDF> m_2; +    std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size()); + +    if (m_0 != 0) +        return boost::none; + +    m_1 = m_1 ^ MGF1<0x20>(m_2); +    m_2 = m_2 ^ MGF1<0xDF>(m_1); + +    const auto offset = FindTicketOffset(m_2); +    if (offset == boost::none) +        return boost::none; +    ASSERT(offset.get() > 0); + +    std::memcpy(key_temp.data(), m_2.data() + offset.get(), key_temp.size()); + +    return std::make_pair(rights_id, key_temp); +} +  KeyManager::KeyManager() {      // Initialize keys      const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); @@ -137,6 +374,15 @@ KeyManager::KeyManager() {      AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);      AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true); +    AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false); +    AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false); +} + +static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { +    if (base.size() < begin + length) +        return false; +    return std::all_of(base.begin() + begin, base.begin() + begin + length, +                       [](u8 c) { return std::isdigit(c); });  }  void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { @@ -158,6 +404,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {          out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());          out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); +        if (out[0].compare(0, 1, "#") == 0) +            continue; +          if (is_title_keys) {              auto rights_id_raw = Common::HexStringToArray<16>(out[0]);              u128 rights_id{}; @@ -174,6 +423,50 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {                  const auto index = s256_file_id.at(out[0]);                  Key256 key = Common::HexStringToArray<32>(out[1]);                  s256_keys[{index.type, index.field1, index.field2}] = key; +            } else if (out[0].compare(0, 8, "keyblob_") == 0 && +                       out[0].compare(0, 9, "keyblob_k") != 0) { +                if (!ValidCryptoRevisionString(out[0], 8, 2)) +                    continue; + +                const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); +                keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); +            } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { +                if (!ValidCryptoRevisionString(out[0], 18, 2)) +                    continue; + +                const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); +                encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); +            } else { +                for (const auto& kv : KEYS_VARIABLE_LENGTH) { +                    if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) +                        continue; +                    if (out[0].compare(0, kv.second.size(), kv.second) == 0) { +                        const auto index = +                            std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); +                        const auto sub = kv.first.second; +                        if (sub == 0) { +                            s128_keys[{kv.first.first, index, 0}] = +                                Common::HexStringToArray<16>(out[1]); +                        } else { +                            s128_keys[{kv.first.first, kv.first.second, index}] = +                                Common::HexStringToArray<16>(out[1]); +                        } + +                        break; +                    } +                } + +                static constexpr std::array<const char*, 3> kak_names = { +                    "key_area_key_application_", "key_area_key_ocean_", "key_area_key_system_"}; +                for (size_t j = 0; j < kak_names.size(); ++j) { +                    const auto& match = kak_names[j]; +                    if (out[0].compare(0, std::strlen(match), match) == 0) { +                        const auto index = +                            std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); +                        s128_keys[{S128KeyType::KeyArea, index, j}] = +                            Common::HexStringToArray<16>(out[1]); +                    } +                }              }          }      } @@ -187,6 +480,28 @@ void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string&          LoadFromFile(dir2 + DIR_SEP + filename, title);  } +bool KeyManager::BaseDeriveNecessary() const { +    const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) { +        return !HasKey(key_type, index1, index2); +    }; + +    if (check_key_existence(S256KeyType::Header)) +        return true; + +    for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) { +        if (check_key_existence(S128KeyType::Master, i) || +            check_key_existence(S128KeyType::KeyArea, i, +                                static_cast<u64>(KeyAreaKeyType::Application)) || +            check_key_existence(S128KeyType::KeyArea, i, static_cast<u64>(KeyAreaKeyType::Ocean)) || +            check_key_existence(S128KeyType::KeyArea, i, +                                static_cast<u64>(KeyAreaKeyType::System)) || +            check_key_existence(S128KeyType::Titlekek, i)) +            return true; +    } + +    return false; +} +  bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const {      return s128_keys.find({id, field1, field2}) != s128_keys.end();  } @@ -207,13 +522,30 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {      return s256_keys.at({id, field1, field2});  } -template <std::size_t Size> -void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, +Key256 KeyManager::GetBISKey(u8 partition_id) const { +    Key256 out{}; + +    for (const auto& bis_type : {BISKeyType::Crypto, BISKeyType::Tweak}) { +        if (HasKey(S128KeyType::BIS, partition_id, static_cast<u64>(bis_type))) { +            std::memcpy( +                out.data() + sizeof(Key128) * static_cast<u64>(bis_type), +                s128_keys.at({S128KeyType::BIS, partition_id, static_cast<u64>(bis_type)}).data(), +                sizeof(Key128)); +        } +    } + +    return out; +} + +template <size_t Size> +void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,                                  const std::array<u8, Size>& key) {      const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);      std::string filename = "title.keys_autogenerated"; -    if (!title_key) +    if (category == KeyCategory::Standard)          filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; +    else if (category == KeyCategory::Console) +        filename = "console.keys_autogenerated";      const auto add_info_text = !FileUtil::Exists(yuzu_keys_dir + DIR_SEP + filename);      FileUtil::CreateFullPath(yuzu_keys_dir + DIR_SEP + filename);      std::ofstream file(yuzu_keys_dir + DIR_SEP + filename, std::ios::app); @@ -227,7 +559,7 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,      }      file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key)); -    AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key); +    AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title);  }  void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { @@ -237,8 +569,15 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {          Key128 rights_id;          std::memcpy(rights_id.data(), &field2, sizeof(u64));          std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64)); -        WriteKeyToFile(true, Common::HexArrayToString(rights_id), key); +        WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key);      } + +    auto category = KeyCategory::Standard; +    if (id == S128KeyType::Keyblob || id == S128KeyType::KeyblobMAC || id == S128KeyType::TSEC || +        id == S128KeyType::SecureBoot || id == S128KeyType::SDSeed || id == S128KeyType::BIS) { +        category = KeyCategory::Console; +    } +      const auto iter2 = std::find_if(          s128_file_id.begin(), s128_file_id.end(),          [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) { @@ -246,7 +585,30 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {                     std::tie(id, field1, field2);          });      if (iter2 != s128_file_id.end()) -        WriteKeyToFile(false, iter2->first, key); +        WriteKeyToFile(category, iter2->first, key); + +    // Variable cases +    if (id == S128KeyType::KeyArea) { +        static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}", +                                                                 "key_area_key_ocean_{:02X}", +                                                                 "key_area_key_system_{:02X}"}; +        WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key); +    } else if (id == S128KeyType::Master) { +        WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key); +    } else if (id == S128KeyType::Package1) { +        WriteKeyToFile(category, fmt::format("package1_key_{:02X}", field1), key); +    } else if (id == S128KeyType::Package2) { +        WriteKeyToFile(category, fmt::format("package2_key_{:02X}", field1), key); +    } else if (id == S128KeyType::Titlekek) { +        WriteKeyToFile(category, fmt::format("titlekek_{:02X}", field1), key); +    } else if (id == S128KeyType::Keyblob) { +        WriteKeyToFile(category, fmt::format("keyblob_key_{:02X}", field1), key); +    } else if (id == S128KeyType::KeyblobMAC) { +        WriteKeyToFile(category, fmt::format("keyblob_mac_key_{:02X}", field1), key); +    } else if (id == S128KeyType::Source && field1 == static_cast<u64>(SourceKeyType::Keyblob)) { +        WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key); +    } +      s128_keys[{id, field1, field2}] = key;  } @@ -260,7 +622,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {                     std::tie(id, field1, field2);          });      if (iter != s256_file_id.end()) -        WriteKeyToFile(false, iter->first, key); +        WriteKeyToFile(KeyCategory::Standard, iter->first, key);      s256_keys[{id, field1, field2}] = key;  } @@ -290,59 +652,388 @@ void KeyManager::DeriveSDSeedLazy() {          SetKey(S128KeyType::SDSeed, res.get());  } +static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) { +    Key128 out{}; + +    mbedtls_cipher_cmac(mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB), key.data(), +                        key.size() * 8, source, size, out.data()); +    return out; +} + +void KeyManager::DeriveBase() { +    if (!BaseDeriveNecessary()) +        return; + +    if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC)) +        return; + +    const auto has_bis = [this](u64 id) { +        return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) && +               HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Tweak)); +    }; + +    const auto copy_bis = [this](u64 id_from, u64 id_to) { +        SetKey(S128KeyType::BIS, +               GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Crypto)), id_to, +               static_cast<u64>(BISKeyType::Crypto)); + +        SetKey(S128KeyType::BIS, +               GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Tweak)), id_to, +               static_cast<u64>(BISKeyType::Tweak)); +    }; + +    if (has_bis(2) && !has_bis(3)) +        copy_bis(2, 3); +    else if (has_bis(3) && !has_bis(2)) +        copy_bis(3, 2); + +    std::bitset<32> revisions(0xFFFFFFFF); +    for (size_t i = 0; i < revisions.size(); ++i) { +        if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i) || +            encrypted_keyblobs[i] == std::array<u8, 0xB0>{}) { +            revisions.reset(i); +        } +    } + +    if (!revisions.any()) +        return; + +    const auto sbk = GetKey(S128KeyType::SecureBoot); +    const auto tsec = GetKey(S128KeyType::TSEC); +    const auto master_source = GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master)); + +    for (size_t i = 0; i < revisions.size(); ++i) { +        if (!revisions[i]) +            continue; + +        // Derive keyblob key +        const auto key = DeriveKeyblobKey( +            sbk, tsec, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i)); + +        SetKey(S128KeyType::Keyblob, key, i); + +        // Derive keyblob MAC key +        if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))) +            continue; + +        const auto mac_key = DeriveKeyblobMACKey( +            key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))); +        SetKey(S128KeyType::KeyblobMAC, mac_key, i); + +        Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key); +        if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0) +            continue; + +        // Decrypt keyblob +        if (keyblobs[i] == std::array<u8, 0x90>{}) { +            keyblobs[i] = DecryptKeyblob(encrypted_keyblobs[i], key); +            WriteKeyToFile<0x90>(KeyCategory::Console, fmt::format("keyblob_{:02X}", i), +                                 keyblobs[i]); +        } + +        Key128 package1; +        std::memcpy(package1.data(), keyblobs[i].data() + 0x80, sizeof(Key128)); +        SetKey(S128KeyType::Package1, package1, i); + +        // Derive master key +        if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master))) { +            SetKey(S128KeyType::Master, +                   DeriveMasterKey(keyblobs[i], GetKey(S128KeyType::Source, +                                                       static_cast<u64>(SourceKeyType::Master))), +                   i); +        } +    } + +    revisions.set(); +    for (size_t i = 0; i < revisions.size(); ++i) { +        if (!HasKey(S128KeyType::Master, i)) +            revisions.reset(i); +    } + +    if (!revisions.any()) +        return; + +    for (size_t i = 0; i < revisions.size(); ++i) { +        if (!revisions[i]) +            continue; + +        // Derive general purpose keys +        DeriveGeneralPurposeKeys(i); +    } + +    if (HasKey(S128KeyType::Master, 0) && +        HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)) && +        HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)) && +        HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)) && +        HasKey(S256KeyType::HeaderSource)) { +        const auto header_kek = GenerateKeyEncryptionKey( +            GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)), +            GetKey(S128KeyType::Master, 0), +            GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)), +            GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration))); +        SetKey(S128KeyType::HeaderKek, header_kek); + +        AESCipher<Key128> header_cipher(header_kek, Mode::ECB); +        Key256 out = GetKey(S256KeyType::HeaderSource); +        header_cipher.Transcode(out.data(), out.size(), out.data(), Op::Decrypt); +        SetKey(S256KeyType::Header, out); +    } +} + +void KeyManager::DeriveETicket(PartitionDataManager& data) { +    // ETicket keys +    const auto es = Service::FileSystem::GetUnionContents()->GetEntry( +        0x0100000000000033, FileSys::ContentRecordType::Program); + +    if (es == nullptr) +        return; + +    const auto exefs = es->GetExeFS(); +    if (exefs == nullptr) +        return; + +    const auto main = exefs->GetFile("main"); +    if (main == nullptr) +        return; + +    const auto bytes = main->ReadAllBytes(); + +    const auto eticket_kek = FindKeyFromHex16(bytes, eticket_source_hashes[0]); +    const auto eticket_kekek = FindKeyFromHex16(bytes, eticket_source_hashes[1]); + +    const auto seed3 = data.GetRSAKekSeed3(); +    const auto mask0 = data.GetRSAKekMask0(); + +    if (eticket_kek != Key128{}) +        SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek)); +    if (eticket_kekek != Key128{}) { +        SetKey(S128KeyType::Source, eticket_kekek, +               static_cast<size_t>(SourceKeyType::ETicketKekek)); +    } +    if (seed3 != Key128{}) +        SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3)); +    if (mask0 != Key128{}) +        SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0)); +    if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} || +        mask0 == Key128{}) { +        return; +    } + +    Key128 rsa_oaep_kek{}; +    std::transform(seed3.begin(), seed3.end(), mask0.begin(), rsa_oaep_kek.begin(), +                   std::bit_xor<>()); + +    if (rsa_oaep_kek == Key128{}) +        return; + +    SetKey(S128KeyType::Source, rsa_oaep_kek, +           static_cast<u64>(SourceKeyType::RSAOaepKekGeneration)); + +    Key128 temp_kek{}; +    Key128 temp_kekek{}; +    Key128 eticket_final{}; + +    // Derive ETicket RSA Kek +    AESCipher<Key128> es_master(GetKey(S128KeyType::Master), Mode::ECB); +    es_master.Transcode(rsa_oaep_kek.data(), rsa_oaep_kek.size(), temp_kek.data(), Op::Decrypt); +    AESCipher<Key128> es_kekek(temp_kek, Mode::ECB); +    es_kekek.Transcode(eticket_kekek.data(), eticket_kekek.size(), temp_kekek.data(), Op::Decrypt); +    AESCipher<Key128> es_kek(temp_kekek, Mode::ECB); +    es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt); + +    if (eticket_final == Key128{}) +        return; + +    SetKey(S128KeyType::ETicketRSAKek, eticket_final); + +    // Titlekeys +    data.DecryptProdInfo(GetBISKey(0)); + +    const auto eticket_extended_kek = data.GetETicketExtendedKek(); + +    std::vector<u8> extended_iv(0x10); +    std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); +    std::array<u8, 0x230> extended_dec{}; +    AESCipher<Key128> rsa_1(eticket_final, Mode::CTR); +    rsa_1.SetIV(extended_iv); +    rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, +                    extended_dec.data(), Op::Decrypt); + +    RSAKeyPair<2048> rsa_key{}; +    std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); +    std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); +    std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); + +    const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + +                                     "/system/save/80000000000000e1", +                                 "rb+"); +    const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + +                                     "/system/save/80000000000000e2", +                                 "rb+"); + +    auto res = GetTicketblob(save1); +    const auto res2 = GetTicketblob(save2); +    std::copy(res2.begin(), res2.end(), std::back_inserter(res)); + +    for (const auto& raw : res) { +        const auto pair = ParseTicket(raw, rsa_key); +        if (pair == boost::none) +            continue; +        const auto& [rid, key] = pair.value(); +        u128 rights_id; +        std::memcpy(rights_id.data(), rid.data(), rid.size()); +        SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); +    } +} + +void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { +    if (key == Key128{}) +        return; +    SetKey(id, key, field1, field2); +} + +void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) { +    if (key == Key256{}) +        return; +    SetKey(id, key, field1, field2); +} + +void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { +    if (!BaseDeriveNecessary()) +        return; + +    if (!data.HasBoot0()) +        return; + +    for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) { +        if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{}) +            continue; +        encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i); +        WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i), +                             encrypted_keyblobs[i]); +    } + +    SetKeyWrapped(S128KeyType::Source, data.GetPackage2KeySource(), +                  static_cast<u64>(SourceKeyType::Package2)); +    SetKeyWrapped(S128KeyType::Source, data.GetAESKekGenerationSource(), +                  static_cast<u64>(SourceKeyType::AESKekGeneration)); +    SetKeyWrapped(S128KeyType::Source, data.GetTitlekekSource(), +                  static_cast<u64>(SourceKeyType::Titlekek)); +    SetKeyWrapped(S128KeyType::Source, data.GetMasterKeySource(), +                  static_cast<u64>(SourceKeyType::Master)); +    SetKeyWrapped(S128KeyType::Source, data.GetKeyblobMACKeySource(), +                  static_cast<u64>(SourceKeyType::KeyblobMAC)); + +    for (size_t i = 0; i < PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH; ++i) { +        SetKeyWrapped(S128KeyType::Source, data.GetKeyblobKeySource(i), +                      static_cast<u64>(SourceKeyType::Keyblob), i); +    } + +    if (data.HasFuses()) +        SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey()); + +    DeriveBase(); + +    Key128 latest_master{}; +    for (s8 i = 0x1F; i >= 0; --i) { +        if (GetKey(S128KeyType::Master, static_cast<u8>(i)) != Key128{}) { +            latest_master = GetKey(S128KeyType::Master, static_cast<u8>(i)); +            break; +        } +    } + +    const auto masters = data.GetTZMasterKeys(latest_master); +    for (size_t i = 0; i < masters.size(); ++i) { +        if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i)) +            SetKey(S128KeyType::Master, masters[i], i); +    } + +    DeriveBase(); + +    if (!data.HasPackage2()) +        return; + +    std::array<Key128, 0x20> package2_keys{}; +    for (size_t i = 0; i < package2_keys.size(); ++i) { +        if (HasKey(S128KeyType::Package2, i)) +            package2_keys[i] = GetKey(S128KeyType::Package2, i); +    } +    data.DecryptPackage2(package2_keys, Package2Type::NormalMain); + +    SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyApplicationSource(), +                  static_cast<u64>(SourceKeyType::KeyAreaKey), +                  static_cast<u64>(KeyAreaKeyType::Application)); +    SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyOceanSource(), +                  static_cast<u64>(SourceKeyType::KeyAreaKey), +                  static_cast<u64>(KeyAreaKeyType::Ocean)); +    SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeySystemSource(), +                  static_cast<u64>(SourceKeyType::KeyAreaKey), +                  static_cast<u64>(KeyAreaKeyType::System)); +    SetKeyWrapped(S128KeyType::Source, data.GetSDKekSource(), +                  static_cast<u64>(SourceKeyType::SDKek)); +    SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDSaveKeySource(), +                  static_cast<u64>(SDKeyType::Save)); +    SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDNCAKeySource(), +                  static_cast<u64>(SDKeyType::NCA)); +    SetKeyWrapped(S128KeyType::Source, data.GetHeaderKekSource(), +                  static_cast<u64>(SourceKeyType::HeaderKek)); +    SetKeyWrapped(S256KeyType::HeaderSource, data.GetHeaderKeySource()); +    SetKeyWrapped(S128KeyType::Source, data.GetAESKeyGenerationSource(), +                  static_cast<u64>(SourceKeyType::AESKeyGeneration)); + +    DeriveBase(); +} +  const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { -    {"master_key_00", {S128KeyType::Master, 0, 0}}, -    {"master_key_01", {S128KeyType::Master, 1, 0}}, -    {"master_key_02", {S128KeyType::Master, 2, 0}}, -    {"master_key_03", {S128KeyType::Master, 3, 0}}, -    {"master_key_04", {S128KeyType::Master, 4, 0}}, -    {"package1_key_00", {S128KeyType::Package1, 0, 0}}, -    {"package1_key_01", {S128KeyType::Package1, 1, 0}}, -    {"package1_key_02", {S128KeyType::Package1, 2, 0}}, -    {"package1_key_03", {S128KeyType::Package1, 3, 0}}, -    {"package1_key_04", {S128KeyType::Package1, 4, 0}}, -    {"package2_key_00", {S128KeyType::Package2, 0, 0}}, -    {"package2_key_01", {S128KeyType::Package2, 1, 0}}, -    {"package2_key_02", {S128KeyType::Package2, 2, 0}}, -    {"package2_key_03", {S128KeyType::Package2, 3, 0}}, -    {"package2_key_04", {S128KeyType::Package2, 4, 0}}, -    {"titlekek_00", {S128KeyType::Titlekek, 0, 0}}, -    {"titlekek_01", {S128KeyType::Titlekek, 1, 0}}, -    {"titlekek_02", {S128KeyType::Titlekek, 2, 0}}, -    {"titlekek_03", {S128KeyType::Titlekek, 3, 0}}, -    {"titlekek_04", {S128KeyType::Titlekek, 4, 0}},      {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, -    {"key_area_key_application_00", -     {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}}, -    {"key_area_key_application_01", -     {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}}, -    {"key_area_key_application_02", -     {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}}, -    {"key_area_key_application_03", -     {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}}, -    {"key_area_key_application_04", -     {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}}, -    {"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}}, -    {"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}}, -    {"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}}, -    {"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}}, -    {"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}}, -    {"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}}, -    {"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}}, -    {"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}}, -    {"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}}, -    {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}}, -    {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK), 0}}, +    {"eticket_rsa_kek_source", +     {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}}, +    {"eticket_rsa_kekek_source", +     {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}}, +    {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}}, +    {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}}, +    {"rsa_oaep_kek_generation_source", +     {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}}, +    {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},      {"aes_kek_generation_source", -     {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration), 0}}, +     {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},      {"aes_key_generation_source",       {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}}, +    {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}}, +    {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}}, +    {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}}, +    {"key_area_key_application_source", +     {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), +      static_cast<u64>(KeyAreaKeyType::Application)}}, +    {"key_area_key_ocean_source", +     {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), +      static_cast<u64>(KeyAreaKeyType::Ocean)}}, +    {"key_area_key_system_source", +     {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), +      static_cast<u64>(KeyAreaKeyType::System)}}, +    {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}}, +    {"keyblob_mac_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)}}, +    {"tsec_key", {S128KeyType::TSEC, 0, 0}}, +    {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},      {"sd_seed", {S128KeyType::SDSeed, 0, 0}}, +    {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}}, +    {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}}, +    {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}}, +    {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}}, +    {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}}, +    {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}}, +    {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}}, +    {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}}, +    {"header_kek", {S128KeyType::HeaderKek, 0, 0}}, +    {"sd_card_kek", {S128KeyType::SDKek, 0, 0}},  };  const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {      {"header_key", {S256KeyType::Header, 0, 0}},      {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},      {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}}, +    {"header_key_source", {S256KeyType::HeaderSource, 0, 0}}, +    {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}}, +    {"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},  };  } // namespace Core::Crypto diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 978eec8dc..a41abbdfc 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -5,11 +5,18 @@  #pragma once  #include <array> +#include <map>  #include <string>  #include <boost/container/flat_map.hpp>  #include <boost/optional.hpp>  #include <fmt/format.h>  #include "common/common_types.h" +#include "core/crypto/partition_data_manager.h" +#include "core/file_sys/vfs_types.h" + +namespace FileUtil { +class IOFile; +}  namespace Loader {  enum class ResultStatus : u16; @@ -22,13 +29,30 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;  using Key128 = std::array<u8, 0x10>;  using Key256 = std::array<u8, 0x20>;  using SHA256Hash = std::array<u8, 0x20>; +using TicketRaw = std::array<u8, 0x400>;  static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); -static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big."); +static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); + +template <size_t bit_size, size_t byte_size = (bit_size >> 3)> +struct RSAKeyPair { +    std::array<u8, byte_size> encryption_key; +    std::array<u8, byte_size> decryption_key; +    std::array<u8, byte_size> modulus; +    std::array<u8, 4> exponent; +}; + +enum class KeyCategory : u8 { +    Standard, +    Title, +    Console, +};  enum class S256KeyType : u64 { -    Header,      // -    SDKeySource, // f1=SDKeyType +    SDKey,        // f1=SDKeyType +    Header,       // +    SDKeySource,  // f1=SDKeyType +    HeaderSource, //  };  enum class S128KeyType : u64 { @@ -41,6 +65,14 @@ enum class S128KeyType : u64 {      SDSeed,        //      Titlekey,      // f1=rights id LSB f2=rights id MSB      Source,        // f1=source type, f2= sub id +    Keyblob,       // f1=crypto revision +    KeyblobMAC,    // f1=crypto revision +    TSEC,          // +    SecureBoot,    // +    BIS,           // f1=partition (0-3), f2=type {crypt, tweak} +    HeaderKek,     // +    SDKek,         // +    RSAKek,        //  };  enum class KeyAreaKeyType : u8 { @@ -50,9 +82,19 @@ enum class KeyAreaKeyType : u8 {  };  enum class SourceKeyType : u8 { -    SDKEK, -    AESKEKGeneration, -    AESKeyGeneration, +    SDKek,                // +    AESKekGeneration,     // +    AESKeyGeneration,     // +    RSAOaepKekGeneration, // +    Master,               // +    Keyblob,              // f2=crypto revision +    KeyAreaKey,           // f2=KeyAreaKeyType +    Titlekek,             // +    Package2,             // +    HeaderKek,            // +    KeyblobMAC,           // +    ETicketKek,           // +    ETicketKekek,         //  };  enum class SDKeyType : u8 { @@ -60,6 +102,16 @@ enum class SDKeyType : u8 {      NCA,  }; +enum class BISKeyType : u8 { +    Crypto, +    Tweak, +}; + +enum class RSAKekType : u8 { +    Mask0, +    Seed3, +}; +  template <typename KeyType>  struct KeyIndex {      KeyType type; @@ -91,6 +143,8 @@ public:      Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;      Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const; +    Key256 GetBISKey(u8 partition_id) const; +      void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);      void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); @@ -100,23 +154,51 @@ public:      // 8*43 and the private file to exist.      void DeriveSDSeedLazy(); +    bool BaseDeriveNecessary() const; +    void DeriveBase(); +    void DeriveETicket(PartitionDataManager& data); + +    void PopulateFromPartitionData(PartitionDataManager& data); +  private: -    boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys; -    boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys; +    std::map<KeyIndex<S128KeyType>, Key128> s128_keys; +    std::map<KeyIndex<S256KeyType>, Key256> s256_keys; + +    std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; +    std::array<std::array<u8, 0x90>, 0x20> keyblobs{};      bool dev_mode;      void LoadFromFile(const std::string& filename, bool is_title_keys);      void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,                              const std::string& filename, bool title); -    template <std::size_t Size> -    void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key); +    template <size_t Size> +    void WriteKeyToFile(KeyCategory category, std::string_view keyname, +                        const std::array<u8, Size>& key); + +    void DeriveGeneralPurposeKeys(u8 crypto_revision); + +    void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); +    void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);      static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id;      static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id;  };  Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); +Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source); +Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source); +Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source); +std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblob, +                                    const Key128& key); +  boost::optional<Key128> DeriveSDSeed(); -Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys); +Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); + +std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save); + +// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset +// 0x140-0x144 is zero) +boost::optional<std::pair<Key128, Key128>> ParseTicket( +    const TicketRaw& ticket, const RSAKeyPair<2048>& eticket_extended_key);  } // namespace Core::Crypto diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp new file mode 100644 index 000000000..d1c04e98d --- /dev/null +++ b/src/core/crypto/partition_data_manager.cpp @@ -0,0 +1,601 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// NOTE TO FUTURE MAINTAINERS: +// When a new version of switch cryptography is released, +// hash the new keyblob source and master key and add the hashes to +// the arrays below. + +#include <algorithm> +#include <array> +#include <cctype> +#include <cstring> +#include <boost/optional/optional.hpp> +#include <mbedtls/sha256.h> +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/crypto/ctr_encryption_layer.h" +#include "core/crypto/key_manager.h" +#include "core/crypto/partition_data_manager.h" +#include "core/crypto/xts_encryption_layer.h" +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_offset.h" + +using namespace Common; + +namespace Core::Crypto { + +struct Package2Header { +    std::array<u8, 0x100> signature; +    Key128 header_ctr; +    std::array<Key128, 4> section_ctr; +    u32_le magic; +    u32_le base_offset; +    INSERT_PADDING_BYTES(4); +    u8 version_max; +    u8 version_min; +    INSERT_PADDING_BYTES(2); +    std::array<u32_le, 4> section_size; +    std::array<u32_le, 4> section_offset; +    std::array<SHA256Hash, 4> section_hash; +}; +static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size."); + +struct INIHeader { +    u32_le magic; +    u32_le size; +    u32_le process_count; +    INSERT_PADDING_BYTES(4); +}; +static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size."); + +struct SectionHeader { +    u32_le offset; +    u32_le size_decompressed; +    u32_le size_compressed; +    u32_le attribute; +}; +static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size."); + +struct KIPHeader { +    u32_le magic; +    std::array<char, 12> name; +    u64_le title_id; +    u32_le category; +    u8 priority; +    u8 core; +    INSERT_PADDING_BYTES(1); +    u8 flags; +    std::array<SectionHeader, 6> sections; +    std::array<u32, 0x20> capabilities; +}; +static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size."); + +const std::array<SHA256Hash, 0x10> source_hashes{ +    "B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source +    "7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source +    "21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"_array32, // package2_key_source +    "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // aes_kek_generation_source +    "FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"_array32, // aes_key_generation_source +    "C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"_array32, // titlekek_source +    "04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"_array32, // key_area_key_application_source +    "FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"_array32, // key_area_key_ocean_source +    "1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"_array32, // key_area_key_system_source +    "6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"_array32, // sd_card_kek_source +    "D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"_array32, // sd_card_save_key_source +    "2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"_array32, // sd_card_nca_key_source +    "1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"_array32, // header_kek_source +    "8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"_array32, // header_key_source +    "D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"_array32, // rsa_kek_seed3 +    "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // rsa_kek_mask0 +}; + +const std::array<SHA256Hash, 0x20> keyblob_source_hashes{ +    "8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"_array32, // keyblob_key_source_00 +    "2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"_array32, // keyblob_key_source_01 +    "61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"_array32, // keyblob_key_source_02 +    "8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"_array32, // keyblob_key_source_03 +    "95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"_array32, // keyblob_key_source_04 +    "3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"_array32, // keyblob_key_source_05 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_06 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_07 + +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_08 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_09 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0A +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0B +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0C +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0D +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0E +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0F + +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_10 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_11 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_12 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_13 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_14 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_15 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_16 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_17 + +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_18 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_19 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1A +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1B +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1C +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1D +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1E +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1F +}; + +const std::array<SHA256Hash, 0x20> master_key_hashes{ +    "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32, // master_key_00 +    "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32, // master_key_01 +    "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32, // master_key_02 +    "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32, // master_key_03 +    "75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"_array32, // master_key_04 +    "EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"_array32, // master_key_05 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_06 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_07 + +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_08 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_09 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0A +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0B +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0C +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0D +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0E +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0F + +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_10 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_11 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_12 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_13 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_14 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_15 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_16 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_17 + +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_18 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_19 +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1A +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1B +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1C +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1D +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1E +    "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F +}; + +static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) { +    const auto data_size = in.size() - 0xC; + +    u32 compressed_size{}; +    u32 init_index{}; +    u32 additional_size{}; +    std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32)); +    std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32)); +    std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32)); + +    std::vector<u8> out(in.size() + additional_size); + +    if (compressed_size == in.size()) +        std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size); +    else +        std::memcpy(out.data(), in.data(), compressed_size); + +    auto index = in.size() - init_index; +    auto out_index = out.size(); + +    while (out_index > 0) { +        --index; +        auto control = in[index]; +        for (size_t i = 0; i < 8; ++i) { +            if ((control & 0x80) > 0) { +                ASSERT(index >= 2); +                index -= 2; +                u64 segment_offset = in[index] | in[index + 1] << 8; +                u64 segment_size = ((segment_offset >> 12) & 0xF) + 3; +                segment_offset &= 0xFFF; +                segment_offset += 3; + +                if (out_index < segment_size) +                    segment_size = out_index; + +                ASSERT(out_index >= segment_size); + +                out_index -= segment_size; + +                for (size_t j = 0; j < segment_size; ++j) { +                    ASSERT(out_index + j + segment_offset < out.size()); +                    out[out_index + j] = out[out_index + j + segment_offset]; +                } +            } else { +                ASSERT(out_index >= 1); +                --out_index; +                --index; +                out[out_index] = in[index]; +            } + +            control <<= 1; +            if (out_index == 0) +                return out; +        } +    } + +    return out; +} + +static u8 CalculateMaxKeyblobSourceHash() { +    for (s8 i = 0x1F; i >= 0; --i) { +        if (keyblob_source_hashes[i] != SHA256Hash{}) +            return static_cast<u8>(i + 1); +    } + +    return 0; +} + +const u8 PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH = CalculateMaxKeyblobSourceHash(); + +template <size_t key_size = 0x10> +std::array<u8, key_size> FindKeyFromHex(const std::vector<u8>& binary, +                                        const std::array<u8, 0x20>& hash) { +    if (binary.size() < key_size) +        return {}; + +    std::array<u8, 0x20> temp{}; +    for (size_t i = 0; i < binary.size() - key_size; ++i) { +        mbedtls_sha256(binary.data() + i, key_size, temp.data(), 0); + +        if (temp != hash) +            continue; + +        std::array<u8, key_size> out{}; +        std::memcpy(out.data(), binary.data() + i, key_size); +        return out; +    } + +    return {}; +} + +std::array<u8, 16> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 32> hash) { +    return FindKeyFromHex<0x10>(binary, hash); +} + +static std::array<Key128, 0x20> FindEncryptedMasterKeyFromHex(const std::vector<u8>& binary, +                                                              const Key128& key) { +    if (binary.size() < 0x10) +        return {}; + +    SHA256Hash temp{}; +    Key128 dec_temp{}; +    std::array<Key128, 0x20> out{}; +    AESCipher<Key128> cipher(key, Mode::ECB); +    for (size_t i = 0; i < binary.size() - 0x10; ++i) { +        cipher.Transcode(binary.data() + i, dec_temp.size(), dec_temp.data(), Op::Decrypt); +        mbedtls_sha256(dec_temp.data(), dec_temp.size(), temp.data(), 0); + +        for (size_t k = 0; k < out.size(); ++k) { +            if (temp == master_key_hashes[k]) { +                out[k] = dec_temp; +                break; +            } +        } +    } + +    return out; +} + +FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir, +                                            const std::string& name) { +    auto upper = name; +    std::transform(upper.begin(), upper.end(), upper.begin(), [](u8 c) { return std::toupper(c); }); +    for (const auto& fname : {name, name + ".bin", upper, upper + ".BIN"}) { +        if (dir->GetFile(fname) != nullptr) +            return dir->GetFile(fname); +    } + +    return nullptr; +} + +PartitionDataManager::PartitionDataManager(FileSys::VirtualDir sysdata_dir) +    : boot0(FindFileInDirWithNames(sysdata_dir, "BOOT0")), +      fuses(FindFileInDirWithNames(sysdata_dir, "fuse")), +      kfuses(FindFileInDirWithNames(sysdata_dir, "kfuse")), +      package2({ +          FindFileInDirWithNames(sysdata_dir, "BCPKG2-1-Normal-Main"), +          FindFileInDirWithNames(sysdata_dir, "BCPKG2-2-Normal-Sub"), +          FindFileInDirWithNames(sysdata_dir, "BCPKG2-3-SafeMode-Main"), +          FindFileInDirWithNames(sysdata_dir, "BCPKG2-4-SafeMode-Sub"), +          FindFileInDirWithNames(sysdata_dir, "BCPKG2-5-Repair-Main"), +          FindFileInDirWithNames(sysdata_dir, "BCPKG2-6-Repair-Sub"), +      }), +      secure_monitor(FindFileInDirWithNames(sysdata_dir, "secmon")), +      package1_decrypted(FindFileInDirWithNames(sysdata_dir, "pkg1_decr")), +      secure_monitor_bytes(secure_monitor == nullptr ? std::vector<u8>{} +                                                     : secure_monitor->ReadAllBytes()), +      package1_decrypted_bytes(package1_decrypted == nullptr ? std::vector<u8>{} +                                                             : package1_decrypted->ReadAllBytes()), +      prodinfo(FindFileInDirWithNames(sysdata_dir, "PRODINFO")) {} + +PartitionDataManager::~PartitionDataManager() = default; + +bool PartitionDataManager::HasBoot0() const { +    return boot0 != nullptr; +} + +FileSys::VirtualFile PartitionDataManager::GetBoot0Raw() const { +    return boot0; +} + +std::array<u8, 176> PartitionDataManager::GetEncryptedKeyblob(u8 index) const { +    if (HasBoot0() && index < 32) +        return GetEncryptedKeyblobs()[index]; +    return {}; +} + +std::array<std::array<u8, 176>, 32> PartitionDataManager::GetEncryptedKeyblobs() const { +    if (!HasBoot0()) +        return {}; + +    std::array<std::array<u8, 176>, 32> out{}; +    for (size_t i = 0; i < 0x20; ++i) +        boot0->Read(out[i].data(), out[i].size(), 0x180000 + i * 0x200); +    return out; +} + +std::vector<u8> PartitionDataManager::GetSecureMonitor() const { +    return secure_monitor_bytes; +} + +std::array<u8, 16> PartitionDataManager::GetPackage2KeySource() const { +    return FindKeyFromHex(secure_monitor_bytes, source_hashes[2]); +} + +std::array<u8, 16> PartitionDataManager::GetAESKekGenerationSource() const { +    return FindKeyFromHex(secure_monitor_bytes, source_hashes[3]); +} + +std::array<u8, 16> PartitionDataManager::GetTitlekekSource() const { +    return FindKeyFromHex(secure_monitor_bytes, source_hashes[5]); +} + +std::array<std::array<u8, 16>, 32> PartitionDataManager::GetTZMasterKeys( +    std::array<u8, 0x10> master_key) const { +    return FindEncryptedMasterKeyFromHex(secure_monitor_bytes, master_key); +} + +std::array<u8, 16> PartitionDataManager::GetRSAKekSeed3() const { +    return FindKeyFromHex(secure_monitor_bytes, source_hashes[14]); +} + +std::array<u8, 16> PartitionDataManager::GetRSAKekMask0() const { +    return FindKeyFromHex(secure_monitor_bytes, source_hashes[15]); +} + +std::vector<u8> PartitionDataManager::GetPackage1Decrypted() const { +    return package1_decrypted_bytes; +} + +std::array<u8, 16> PartitionDataManager::GetMasterKeySource() const { +    return FindKeyFromHex(package1_decrypted_bytes, source_hashes[1]); +} + +std::array<u8, 16> PartitionDataManager::GetKeyblobMACKeySource() const { +    return FindKeyFromHex(package1_decrypted_bytes, source_hashes[0]); +} + +std::array<u8, 16> PartitionDataManager::GetKeyblobKeySource(u8 revision) const { +    if (keyblob_source_hashes[revision] == SHA256Hash{}) { +        LOG_WARNING(Crypto, +                    "No keyblob source hash for crypto revision {:02X}! Cannot derive keys...", +                    revision); +    } +    return FindKeyFromHex(package1_decrypted_bytes, keyblob_source_hashes[revision]); +} + +bool PartitionDataManager::HasFuses() const { +    return fuses != nullptr; +} + +FileSys::VirtualFile PartitionDataManager::GetFusesRaw() const { +    return fuses; +} + +std::array<u8, 16> PartitionDataManager::GetSecureBootKey() const { +    if (!HasFuses()) +        return {}; +    Key128 out{}; +    fuses->Read(out.data(), out.size(), 0xA4); +    return out; +} + +bool PartitionDataManager::HasKFuses() const { +    return kfuses != nullptr; +} + +FileSys::VirtualFile PartitionDataManager::GetKFusesRaw() const { +    return kfuses; +} + +bool PartitionDataManager::HasPackage2(Package2Type type) const { +    return package2.at(static_cast<size_t>(type)) != nullptr; +} + +FileSys::VirtualFile PartitionDataManager::GetPackage2Raw(Package2Type type) const { +    return package2.at(static_cast<size_t>(type)); +} + +bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) { + +    const std::vector<u8> iv(header.header_ctr.begin(), header.header_ctr.end()); +    Package2Header temp = header; +    AESCipher<Key128> cipher(key, Mode::CTR); +    cipher.SetIV(iv); +    cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr, +                     Op::Decrypt); +    if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) { +        header = temp; +        return true; +    } + +    return false; +} + +void PartitionDataManager::DecryptPackage2(std::array<std::array<u8, 16>, 0x20> package2_keys, +                                           Package2Type type) { +    FileSys::VirtualFile file = std::make_shared<FileSys::OffsetVfsFile>( +        package2[static_cast<size_t>(type)], +        package2[static_cast<size_t>(type)]->GetSize() - 0x4000, 0x4000); + +    Package2Header header{}; +    if (file->ReadObject(&header) != sizeof(Package2Header)) +        return; + +    u8 revision = 0xFF; +    if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) { +        for (size_t i = 0; i < package2_keys.size(); ++i) { +            if (AttemptDecrypt(package2_keys[i], header)) +                revision = i; +        } +    } + +    if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) +        return; + +    const std::vector<u8> s1_iv(header.section_ctr[1].begin(), header.section_ctr[1].end()); + +    const auto a = std::make_shared<FileSys::OffsetVfsFile>( +        file, header.section_size[1], header.section_size[0] + sizeof(Package2Header)); + +    auto c = a->ReadAllBytes(); + +    AESCipher<Key128> cipher(package2_keys[revision], Mode::CTR); +    cipher.SetIV(s1_iv); +    cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt); + +    // package2_decrypted[static_cast<size_t>(type)] = s1; + +    INIHeader ini; +    std::memcpy(&ini, c.data(), sizeof(INIHeader)); +    if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1')) +        return; + +    std::map<u64, KIPHeader> kips{}; +    u64 offset = sizeof(INIHeader); +    for (size_t i = 0; i < ini.process_count; ++i) { +        KIPHeader kip; +        std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader)); +        if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1')) +            return; +        kips.emplace(offset, kip); + +        const auto name = +            Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size()); + +        if (name != "FS" && name != "spl") { +            offset += sizeof(KIPHeader) + kip.sections[0].size_compressed + +                      kip.sections[1].size_compressed + kip.sections[2].size_compressed; +            continue; +        } + +        std::vector<u8> text(kip.sections[0].size_compressed); +        std::vector<u8> rodata(kip.sections[1].size_compressed); +        std::vector<u8> data(kip.sections[2].size_compressed); + +        u64 offset_sec = sizeof(KIPHeader) + offset; +        std::memcpy(text.data(), c.data() + offset_sec, text.size()); +        offset_sec += text.size(); +        std::memcpy(rodata.data(), c.data() + offset_sec, rodata.size()); +        offset_sec += rodata.size(); +        std::memcpy(data.data(), c.data() + offset_sec, data.size()); + +        offset += sizeof(KIPHeader) + kip.sections[0].size_compressed + +                  kip.sections[1].size_compressed + kip.sections[2].size_compressed; + +        text = DecompressBLZ(text); +        rodata = DecompressBLZ(rodata); +        data = DecompressBLZ(data); + +        std::vector<u8> out(text.size() + rodata.size() + data.size()); +        std::memcpy(out.data(), text.data(), text.size()); +        std::memcpy(out.data() + text.size(), rodata.data(), rodata.size()); +        std::memcpy(out.data() + text.size() + rodata.size(), data.data(), data.size()); + +        if (name == "FS") +            package2_fs[static_cast<size_t>(type)] = out; +        else if (name == "spl") +            package2_spl[static_cast<size_t>(type)] = out; +    } +} + +const std::vector<u8>& PartitionDataManager::GetPackage2FSDecompressed(Package2Type type) const { +    return package2_fs.at(static_cast<size_t>(type)); +} + +std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyApplicationSource(Package2Type type) const { +    return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[6]); +} + +std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyOceanSource(Package2Type type) const { +    return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[7]); +} + +std::array<u8, 16> PartitionDataManager::GetKeyAreaKeySystemSource(Package2Type type) const { +    return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[8]); +} + +std::array<u8, 16> PartitionDataManager::GetSDKekSource(Package2Type type) const { +    return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[9]); +} + +std::array<u8, 32> PartitionDataManager::GetSDSaveKeySource(Package2Type type) const { +    return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[10]); +} + +std::array<u8, 32> PartitionDataManager::GetSDNCAKeySource(Package2Type type) const { +    return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[11]); +} + +std::array<u8, 16> PartitionDataManager::GetHeaderKekSource(Package2Type type) const { +    return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[12]); +} + +std::array<u8, 32> PartitionDataManager::GetHeaderKeySource(Package2Type type) const { +    return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[13]); +} + +const std::vector<u8>& PartitionDataManager::GetPackage2SPLDecompressed(Package2Type type) const { +    return package2_spl.at(static_cast<size_t>(type)); +} + +std::array<u8, 16> PartitionDataManager::GetAESKeyGenerationSource(Package2Type type) const { +    return FindKeyFromHex(package2_spl.at(static_cast<size_t>(type)), source_hashes[4]); +} + +bool PartitionDataManager::HasProdInfo() const { +    return prodinfo != nullptr; +} + +FileSys::VirtualFile PartitionDataManager::GetProdInfoRaw() const { +    return prodinfo; +} + +void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) { +    if (prodinfo == nullptr) +        return; + +    prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key); +} + +std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const { +    std::array<u8, 0x240> out{}; +    if (prodinfo_decrypted != nullptr) +        prodinfo_decrypted->Read(out.data(), out.size(), 0x3890); +    return out; +} +} // namespace Core::Crypto diff --git a/src/core/crypto/partition_data_manager.h b/src/core/crypto/partition_data_manager.h new file mode 100644 index 000000000..45c7fecfa --- /dev/null +++ b/src/core/crypto/partition_data_manager.h @@ -0,0 +1,105 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" +#include "core/file_sys/vfs_types.h" + +namespace Core::Crypto { + +enum class Package2Type { +    NormalMain, +    NormalSub, +    SafeModeMain, +    SafeModeSub, +    RepairMain, +    RepairSub, +}; + +class PartitionDataManager { +public: +    static const u8 MAX_KEYBLOB_SOURCE_HASH; + +    explicit PartitionDataManager(FileSys::VirtualDir sysdata_dir); +    ~PartitionDataManager(); + +    // BOOT0 +    bool HasBoot0() const; +    FileSys::VirtualFile GetBoot0Raw() const; +    std::array<u8, 0xB0> GetEncryptedKeyblob(u8 index) const; +    std::array<std::array<u8, 0xB0>, 0x20> GetEncryptedKeyblobs() const; +    std::vector<u8> GetSecureMonitor() const; +    std::array<u8, 0x10> GetPackage2KeySource() const; +    std::array<u8, 0x10> GetAESKekGenerationSource() const; +    std::array<u8, 0x10> GetTitlekekSource() const; +    std::array<std::array<u8, 0x10>, 0x20> GetTZMasterKeys(std::array<u8, 0x10> master_key) const; +    std::array<u8, 0x10> GetRSAKekSeed3() const; +    std::array<u8, 0x10> GetRSAKekMask0() const; +    std::vector<u8> GetPackage1Decrypted() const; +    std::array<u8, 0x10> GetMasterKeySource() const; +    std::array<u8, 0x10> GetKeyblobMACKeySource() const; +    std::array<u8, 0x10> GetKeyblobKeySource(u8 revision) const; + +    // Fuses +    bool HasFuses() const; +    FileSys::VirtualFile GetFusesRaw() const; +    std::array<u8, 0x10> GetSecureBootKey() const; + +    // K-Fuses +    bool HasKFuses() const; +    FileSys::VirtualFile GetKFusesRaw() const; + +    // Package2 +    bool HasPackage2(Package2Type type = Package2Type::NormalMain) const; +    FileSys::VirtualFile GetPackage2Raw(Package2Type type = Package2Type::NormalMain) const; +    void DecryptPackage2(std::array<std::array<u8, 16>, 0x20> package2, Package2Type type); +    const std::vector<u8>& GetPackage2FSDecompressed( +        Package2Type type = Package2Type::NormalMain) const; +    std::array<u8, 0x10> GetKeyAreaKeyApplicationSource( +        Package2Type type = Package2Type::NormalMain) const; +    std::array<u8, 0x10> GetKeyAreaKeyOceanSource( +        Package2Type type = Package2Type::NormalMain) const; +    std::array<u8, 0x10> GetKeyAreaKeySystemSource( +        Package2Type type = Package2Type::NormalMain) const; +    std::array<u8, 0x10> GetSDKekSource(Package2Type type = Package2Type::NormalMain) const; +    std::array<u8, 0x20> GetSDSaveKeySource(Package2Type type = Package2Type::NormalMain) const; +    std::array<u8, 0x20> GetSDNCAKeySource(Package2Type type = Package2Type::NormalMain) const; +    std::array<u8, 0x10> GetHeaderKekSource(Package2Type type = Package2Type::NormalMain) const; +    std::array<u8, 0x20> GetHeaderKeySource(Package2Type type = Package2Type::NormalMain) const; +    const std::vector<u8>& GetPackage2SPLDecompressed( +        Package2Type type = Package2Type::NormalMain) const; +    std::array<u8, 0x10> GetAESKeyGenerationSource( +        Package2Type type = Package2Type::NormalMain) const; + +    // PRODINFO +    bool HasProdInfo() const; +    FileSys::VirtualFile GetProdInfoRaw() const; +    void DecryptProdInfo(std::array<u8, 0x20> bis_key); +    std::array<u8, 0x240> GetETicketExtendedKek() const; + +private: +    FileSys::VirtualFile boot0; +    FileSys::VirtualFile fuses; +    FileSys::VirtualFile kfuses; +    std::array<FileSys::VirtualFile, 6> package2; +    FileSys::VirtualFile prodinfo; +    FileSys::VirtualFile secure_monitor; +    FileSys::VirtualFile package1_decrypted; + +    // Processed +    std::array<FileSys::VirtualFile, 6> package2_decrypted; +    FileSys::VirtualFile prodinfo_decrypted; +    std::vector<u8> secure_monitor_bytes; +    std::vector<u8> package1_decrypted_bytes; +    std::array<std::vector<u8>, 6> package2_fs; +    std::array<std::vector<u8>, 6> package2_spl; +}; + +std::array<u8, 0x10> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 0x20> hash); + +} // namespace Core::Crypto diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 270291631..7f0d520ca 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -12,20 +12,12 @@  #include <vector>  #include <boost/optional.hpp>  #include "common/common_types.h" +#include "core/file_sys/vfs_types.h"  namespace FileSys { -class VfsDirectory; -class VfsFile; -class VfsFilesystem; -  enum class Mode : u32; -// Convenience typedefs to use Vfs* interfaces -using VirtualFilesystem = std::shared_ptr<VfsFilesystem>; -using VirtualDir = std::shared_ptr<VfsDirectory>; -using VirtualFile = std::shared_ptr<VfsFile>; -  // An enumeration representing what can be at the end of a path in a VfsFilesystem  enum class VfsEntryType {      None, diff --git a/src/core/file_sys/vfs_types.h b/src/core/file_sys/vfs_types.h new file mode 100644 index 000000000..6215ed7af --- /dev/null +++ b/src/core/file_sys/vfs_types.h @@ -0,0 +1,21 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +namespace FileSys { + +class VfsDirectory; +class VfsFile; +class VfsFilesystem; + +// Declarations for Vfs* pointer types + +using VirtualDir = std::shared_ptr<VfsDirectory>; +using VirtualFile = std::shared_ptr<VfsFile>; +using VirtualFilesystem = std::shared_ptr<VfsFilesystem>; + +} // namespace FileSys diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e11833c5a..fc186dc2d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -31,6 +31,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual  #include <QDialogButtonBox>  #include <QFileDialog>  #include <QMessageBox> +#include <QtConcurrent/QtConcurrent>  #include <QtGui>  #include <QtWidgets>  #include <fmt/format.h> @@ -171,6 +172,9 @@ GMainWindow::GMainWindow()                         .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));      show(); +    // Gen keys if necessary +    OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); +      // Necessary to load titles from nand in gamelist.      Service::FileSystem::CreateFactories(vfs);      game_list->LoadCompatibilityList(); @@ -443,6 +447,8 @@ void GMainWindow::ConnectMenuEvents() {      connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);      // Help +    connect(ui.action_Rederive, &QAction::triggered, this, +            std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning));      connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnAbout);  } @@ -1375,6 +1381,82 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det      }  } +void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { +    if (behavior == ReinitializeKeyBehavior::Warning) { +        const auto res = QMessageBox::information( +            this, tr("Confirm Key Rederivation"), +            tr("You are about to force rederive all of your keys. \nIf you do not know what this " +               "means or what you are doing, \nthis is a potentially destructive action. \nPlease " +               "make " +               "sure this is what you want \nand optionally make backups.\n\nThis will delete your " +               "autogenerated key files and re-run the key derivation module."), +            QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel}); + +        if (res == QMessageBox::Cancel) +            return; + +        FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + +                         "prod.keys_autogenerated"); +        FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + +                         "console.keys_autogenerated"); +        FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + +                         "title.keys_autogenerated"); +    } + +    Core::Crypto::KeyManager keys{}; +    if (keys.BaseDeriveNecessary()) { +        Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory( +            FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)}; + +        const auto function = [this, &keys, &pdm] { +            keys.PopulateFromPartitionData(pdm); +            Service::FileSystem::CreateFactories(vfs); +            keys.DeriveETicket(pdm); +        }; + +        QString errors; + +        if (!pdm.HasFuses()) +            errors += tr("- Missing fuses - Cannot derive SBK\n"); +        if (!pdm.HasBoot0()) +            errors += tr("- Missing BOOT0 - Cannot derive master keys\n"); +        if (!pdm.HasPackage2()) +            errors += tr("- Missing BCPKG2-1-Normal-Main - Cannot derive general keys\n"); +        if (!pdm.HasProdInfo()) +            errors += tr("- Missing PRODINFO - Cannot derive title keys\n"); + +        if (!errors.isEmpty()) { + +            QMessageBox::warning( +                this, tr("Warning Missing Derivation Components"), +                tr("The following are missing from your configuration that may hinder key " +                   "derivation. It will be attempted but may not complete.\n\n") + +                    errors); +        } + +        QProgressDialog prog; +        prog.setRange(0, 0); +        prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your " +                             "system's performance.")); +        prog.setWindowTitle(tr("Deriving Keys")); + +        prog.show(); + +        auto future = QtConcurrent::run(function); +        while (!future.isFinished()) { +            QCoreApplication::processEvents(); +        } + +        prog.close(); +    } + +    Service::FileSystem::CreateFactories(vfs); + +    if (behavior == ReinitializeKeyBehavior::Warning) { +        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +    } +} +  bool GMainWindow::ConfirmClose() {      if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)          return true; diff --git a/src/yuzu/main.h b/src/yuzu/main.h index fe0e9a50a..3663d6aed 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -41,6 +41,11 @@ enum class EmulatedDirectoryTarget {      SDMC,  }; +enum class ReinitializeKeyBehavior { +    NoWarning, +    Warning, +}; +  namespace DiscordRPC {  class DiscordInterface;  } @@ -167,6 +172,7 @@ private slots:      void HideFullscreen();      void ToggleWindowMode();      void OnCoreError(Core::System::ResultStatus, std::string); +    void OnReinitializeKeys(ReinitializeKeyBehavior behavior);  private:      void UpdateStatusBar(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index cb1664b21..9851f507d 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -103,6 +103,7 @@      </property>      <addaction name="action_Report_Compatibility"/>      <addaction name="separator"/> +     <addaction name="action_Rederive"/>      <addaction name="action_About"/>     </widget>     <addaction name="menu_File"/> @@ -159,6 +160,11 @@      <string>&Stop</string>     </property>    </action> +   <action name="action_Rederive"> +     <property name="text"> +       <string>Reinitialize keys...</string> +     </property> +   </action>    <action name="action_About">     <property name="text">      <string>About yuzu</string>  | 
