Просмотр исходного кода

key_manager: Add ETicket key derivation
Derives titlekeys

Zach Hilman 7 лет назад
Родитель
Сommit
d041d6231c
3 измененных файлов с 277 добавлено и 2 удалено
  1. 1 0
      src/core/CMakeLists.txt
  2. 249 0
      src/core/crypto/key_manager.cpp
  3. 27 2
      src/core/crypto/key_manager.h

+ 1 - 0
src/core/CMakeLists.txt

@@ -70,6 +70,7 @@ add_library(core STATIC
     file_sys/vfs_real.cpp
     file_sys/vfs_real.cpp
     file_sys/vfs_real.h
     file_sys/vfs_real.h
     file_sys/vfs_static.h
     file_sys/vfs_static.h
+    file_sys/vfs_types.h
     file_sys/vfs_vector.cpp
     file_sys/vfs_vector.cpp
     file_sys/vfs_vector.h
     file_sys/vfs_vector.h
     file_sys/xts_archive.cpp
     file_sys/xts_archive.cpp

+ 249 - 0
src/core/crypto/key_manager.cpp

@@ -4,18 +4,30 @@
 
 
 #include <algorithm>
 #include <algorithm>
 #include <array>
 #include <array>
+#include <bitset>
 #include <fstream>
 #include <fstream>
 #include <locale>
 #include <locale>
+#include <map>
 #include <sstream>
 #include <sstream>
 #include <string_view>
 #include <string_view>
 #include <tuple>
 #include <tuple>
 #include <vector>
 #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/common_paths.h"
 #include "common/file_util.h"
 #include "common/file_util.h"
 #include "common/hex_util.h"
 #include "common/hex_util.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "core/crypto/aes_util.h"
 #include "core/crypto/aes_util.h"
 #include "core/crypto/key_manager.h"
 #include "core/crypto/key_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/loader/loader.h"
 #include "core/settings.h"
 #include "core/settings.h"
 
 
@@ -23,6 +35,13 @@ namespace Core::Crypto {
 
 
 constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
 constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
 
 
+using namespace Common;
+
+const static std::array<SHA256Hash, 4> eticket_source_hashes{
+    "B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source
+    "E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source
+};
+
 Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
 Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
     Key128 out{};
     Key128 out{};
 
 
@@ -129,9 +148,131 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
                        return source; ///< Return unaltered source to satisfy output requirement.
                        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;
     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) {
+            TicketRaw next{};
+            std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw));
+            offset += next.size();
+            out.push_back(next);
+        }
+    }
+
+    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{};
+    for (size_t i = 0; i < size; ++i)
+        out[i] = lhs[i] ^ rhs[i];
+    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, 0);
+        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;
+}
+
+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));
+
+    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::pair<Key128, Key128>{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);
+
+    u64 offset = 0;
+    for (size_t i = 0x20; i < m_2.size() - 0x10; ++i) {
+        if (m_2[i] == 0x1) {
+            offset = i + 1;
+            break;
+        } else if (m_2[i] != 0x0) {
+            return boost::none;
+        }
+    }
+
+    ASSERT(offset > 0);
+
+    std::memcpy(key_temp.data(), m_2.data() + offset, key_temp.size());
+
+    return std::pair<Key128, Key128>{rights_id, key_temp};
+}
+
 KeyManager::KeyManager() {
 KeyManager::KeyManager() {
     // Initialize keys
     // Initialize keys
     const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
     const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
@@ -609,6 +750,114 @@ void KeyManager::DeriveBase() {
         SetKey(S256KeyType::Header, out);
         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();
+
+    using namespace Common;
+    const auto eticket_kek = FindKeyFromHex(bytes, eticket_source_hashes[0]);
+    const auto eticket_kekek = FindKeyFromHex(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{};
+    for (size_t i = 0; i < rsa_oaep_kek.size(); ++i)
+        rsa_oaep_kek[i] = seed3[i] ^ mask0[i];
+
+    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(GetKey(S128KeyType::BIS),
+                         GetKey(S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)));
+
+    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;
+        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) {
 void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
     if (key == Key128{})
     if (key == Key128{})
         return;
         return;

+ 27 - 2
src/core/crypto/key_manager.h

@@ -5,11 +5,18 @@
 #pragma once
 #pragma once
 
 
 #include <array>
 #include <array>
+#include <map>
 #include <string>
 #include <string>
 #include <boost/container/flat_map.hpp>
 #include <boost/container/flat_map.hpp>
 #include <boost/optional.hpp>
 #include <boost/optional.hpp>
 #include <fmt/format.h>
 #include <fmt/format.h>
 #include "common/common_types.h"
 #include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
+#include "partition_data_manager.h"
+
+namespace FileUtil {
+class IOFile;
+}
 
 
 namespace Loader {
 namespace Loader {
 enum class ResultStatus : u16;
 enum class ResultStatus : u16;
@@ -22,9 +29,18 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
 using Key128 = std::array<u8, 0x10>;
 using Key128 = std::array<u8, 0x10>;
 using Key256 = std::array<u8, 0x20>;
 using Key256 = std::array<u8, 0x20>;
 using SHA256Hash = 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(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 {
 enum class KeyCategory : u8 {
     Standard,
     Standard,
@@ -140,6 +156,8 @@ public:
 
 
     bool BaseDeriveNecessary();
     bool BaseDeriveNecessary();
     void DeriveBase();
     void DeriveBase();
+    void DeriveETicket(PartitionDataManager data);
+
 private:
 private:
     std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
     std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
     std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
     std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
@@ -166,6 +184,13 @@ Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, K
 Key128 DeriveKeyblobKey(Key128 sbk, Key128 tsec, Key128 source);
 Key128 DeriveKeyblobKey(Key128 sbk, Key128 tsec, Key128 source);
 
 
 boost::optional<Key128> DeriveSDSeed();
 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
 } // namespace Core::Crypto