diff options
| -rw-r--r-- | src/core/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/core/crypto/key_manager.cpp | 249 | ||||
| -rw-r--r-- | src/core/crypto/key_manager.h | 29 |
3 files changed, 277 insertions, 2 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e4a676e91..8ad8dc3f4 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -70,6 +70,7 @@ add_library(core STATIC | |||
| 70 | file_sys/vfs_real.cpp | 70 | file_sys/vfs_real.cpp |
| 71 | file_sys/vfs_real.h | 71 | file_sys/vfs_real.h |
| 72 | file_sys/vfs_static.h | 72 | file_sys/vfs_static.h |
| 73 | file_sys/vfs_types.h | ||
| 73 | file_sys/vfs_vector.cpp | 74 | file_sys/vfs_vector.cpp |
| 74 | file_sys/vfs_vector.h | 75 | file_sys/vfs_vector.h |
| 75 | file_sys/xts_archive.cpp | 76 | file_sys/xts_archive.cpp |
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index b37b09772..1328cdd47 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp | |||
| @@ -4,18 +4,30 @@ | |||
| 4 | 4 | ||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include <array> | 6 | #include <array> |
| 7 | #include <bitset> | ||
| 7 | #include <fstream> | 8 | #include <fstream> |
| 8 | #include <locale> | 9 | #include <locale> |
| 10 | #include <map> | ||
| 9 | #include <sstream> | 11 | #include <sstream> |
| 10 | #include <string_view> | 12 | #include <string_view> |
| 11 | #include <tuple> | 13 | #include <tuple> |
| 12 | #include <vector> | 14 | #include <vector> |
| 15 | #include <mbedtls/bignum.h> | ||
| 16 | #include <mbedtls/cipher.h> | ||
| 17 | #include <mbedtls/cmac.h> | ||
| 18 | #include <mbedtls/sha256.h> | ||
| 19 | #include "common/common_funcs.h" | ||
| 13 | #include "common/common_paths.h" | 20 | #include "common/common_paths.h" |
| 14 | #include "common/file_util.h" | 21 | #include "common/file_util.h" |
| 15 | #include "common/hex_util.h" | 22 | #include "common/hex_util.h" |
| 16 | #include "common/logging/log.h" | 23 | #include "common/logging/log.h" |
| 17 | #include "core/crypto/aes_util.h" | 24 | #include "core/crypto/aes_util.h" |
| 18 | #include "core/crypto/key_manager.h" | 25 | #include "core/crypto/key_manager.h" |
| 26 | #include "core/file_sys/content_archive.h" | ||
| 27 | #include "core/file_sys/nca_metadata.h" | ||
| 28 | #include "core/file_sys/partition_filesystem.h" | ||
| 29 | #include "core/file_sys/registered_cache.h" | ||
| 30 | #include "core/hle/service/filesystem/filesystem.h" | ||
| 19 | #include "core/loader/loader.h" | 31 | #include "core/loader/loader.h" |
| 20 | #include "core/settings.h" | 32 | #include "core/settings.h" |
| 21 | 33 | ||
| @@ -23,6 +35,13 @@ namespace Core::Crypto { | |||
| 23 | 35 | ||
| 24 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; | 36 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; |
| 25 | 37 | ||
| 38 | using namespace Common; | ||
| 39 | |||
| 40 | const static std::array<SHA256Hash, 4> eticket_source_hashes{ | ||
| 41 | "B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source | ||
| 42 | "E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source | ||
| 43 | }; | ||
| 44 | |||
| 26 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { | 45 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { |
| 27 | Key128 out{}; | 46 | Key128 out{}; |
| 28 | 47 | ||
| @@ -129,9 +148,131 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke | |||
| 129 | return source; ///< Return unaltered source to satisfy output requirement. | 148 | return source; ///< Return unaltered source to satisfy output requirement. |
| 130 | }); | 149 | }); |
| 131 | 150 | ||
| 151 | keys.SetKey(S256KeyType::SDKey, sd_keys[0], static_cast<u64>(SDKeyType::Save)); | ||
| 152 | keys.SetKey(S256KeyType::SDKey, sd_keys[1], static_cast<u64>(SDKeyType::NCA)); | ||
| 153 | |||
| 132 | return Loader::ResultStatus::Success; | 154 | return Loader::ResultStatus::Success; |
| 133 | } | 155 | } |
| 134 | 156 | ||
| 157 | std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { | ||
| 158 | if (!ticket_save.IsOpen()) | ||
| 159 | return {}; | ||
| 160 | |||
| 161 | std::vector<u8> buffer(ticket_save.GetSize()); | ||
| 162 | ticket_save.ReadBytes(buffer.data(), buffer.size()); | ||
| 163 | |||
| 164 | std::vector<TicketRaw> out; | ||
| 165 | u32 magic{}; | ||
| 166 | for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { | ||
| 167 | if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && | ||
| 168 | buffer[offset + 3] == 0x0) { | ||
| 169 | TicketRaw next{}; | ||
| 170 | std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw)); | ||
| 171 | offset += next.size(); | ||
| 172 | out.push_back(next); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | return out; | ||
| 177 | } | ||
| 178 | |||
| 179 | template <size_t size> | ||
| 180 | static std::array<u8, size> operator^(const std::array<u8, size>& lhs, | ||
| 181 | const std::array<u8, size>& rhs) { | ||
| 182 | std::array<u8, size> out{}; | ||
| 183 | for (size_t i = 0; i < size; ++i) | ||
| 184 | out[i] = lhs[i] ^ rhs[i]; | ||
| 185 | return out; | ||
| 186 | } | ||
| 187 | |||
| 188 | template <size_t target_size, size_t in_size> | ||
| 189 | static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) { | ||
| 190 | std::array<u8, in_size + 4> seed_exp{}; | ||
| 191 | std::memcpy(seed_exp.data(), seed.data(), in_size); | ||
| 192 | |||
| 193 | std::vector<u8> out; | ||
| 194 | size_t i = 0; | ||
| 195 | while (out.size() < target_size) { | ||
| 196 | out.resize(out.size() + 0x20, 0); | ||
| 197 | seed_exp[in_size + 3] = i; | ||
| 198 | mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0); | ||
| 199 | ++i; | ||
| 200 | } | ||
| 201 | |||
| 202 | std::array<u8, target_size> target{}; | ||
| 203 | std::memcpy(target.data(), out.data(), target_size); | ||
| 204 | return target; | ||
| 205 | } | ||
| 206 | |||
| 207 | boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, | ||
| 208 | const RSAKeyPair<2048>& key) { | ||
| 209 | u32 cert_authority; | ||
| 210 | std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority)); | ||
| 211 | if (cert_authority == 0) | ||
| 212 | return boost::none; | ||
| 213 | if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) | ||
| 214 | LOG_INFO(Crypto, | ||
| 215 | "Attempting to parse ticket with non-standard certificate authority {:08X}.", | ||
| 216 | cert_authority); | ||
| 217 | |||
| 218 | Key128 rights_id{}; | ||
| 219 | std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128)); | ||
| 220 | |||
| 221 | Key128 key_temp{}; | ||
| 222 | |||
| 223 | if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) { | ||
| 224 | std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size()); | ||
| 225 | return std::pair<Key128, Key128>{rights_id, key_temp}; | ||
| 226 | } | ||
| 227 | |||
| 228 | mbedtls_mpi D; // RSA Private Exponent | ||
| 229 | mbedtls_mpi N; // RSA Modulus | ||
| 230 | mbedtls_mpi S; // Input | ||
| 231 | mbedtls_mpi M; // Output | ||
| 232 | |||
| 233 | mbedtls_mpi_init(&D); | ||
| 234 | mbedtls_mpi_init(&N); | ||
| 235 | mbedtls_mpi_init(&S); | ||
| 236 | mbedtls_mpi_init(&M); | ||
| 237 | |||
| 238 | mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); | ||
| 239 | mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); | ||
| 240 | mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100); | ||
| 241 | |||
| 242 | mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); | ||
| 243 | |||
| 244 | std::array<u8, 0x100> rsa_step{}; | ||
| 245 | mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size()); | ||
| 246 | |||
| 247 | u8 m_0 = rsa_step[0]; | ||
| 248 | std::array<u8, 0x20> m_1{}; | ||
| 249 | std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size()); | ||
| 250 | std::array<u8, 0xDF> m_2{}; | ||
| 251 | std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size()); | ||
| 252 | |||
| 253 | if (m_0 != 0) | ||
| 254 | return boost::none; | ||
| 255 | |||
| 256 | m_1 = m_1 ^ MGF1<0x20>(m_2); | ||
| 257 | m_2 = m_2 ^ MGF1<0xDF>(m_1); | ||
| 258 | |||
| 259 | u64 offset = 0; | ||
| 260 | for (size_t i = 0x20; i < m_2.size() - 0x10; ++i) { | ||
| 261 | if (m_2[i] == 0x1) { | ||
| 262 | offset = i + 1; | ||
| 263 | break; | ||
| 264 | } else if (m_2[i] != 0x0) { | ||
| 265 | return boost::none; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | ASSERT(offset > 0); | ||
| 270 | |||
| 271 | std::memcpy(key_temp.data(), m_2.data() + offset, key_temp.size()); | ||
| 272 | |||
| 273 | return std::pair<Key128, Key128>{rights_id, key_temp}; | ||
| 274 | } | ||
| 275 | |||
| 135 | KeyManager::KeyManager() { | 276 | KeyManager::KeyManager() { |
| 136 | // Initialize keys | 277 | // Initialize keys |
| 137 | const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); | 278 | const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); |
| @@ -609,6 +750,114 @@ void KeyManager::DeriveBase() { | |||
| 609 | SetKey(S256KeyType::Header, out); | 750 | SetKey(S256KeyType::Header, out); |
| 610 | } | 751 | } |
| 611 | } | 752 | } |
| 753 | |||
| 754 | void KeyManager::DeriveETicket(PartitionDataManager data) { | ||
| 755 | // ETicket keys | ||
| 756 | const auto es = Service::FileSystem::GetUnionContents()->GetEntry( | ||
| 757 | 0x0100000000000033, FileSys::ContentRecordType::Program); | ||
| 758 | |||
| 759 | if (es == nullptr) | ||
| 760 | return; | ||
| 761 | |||
| 762 | const auto exefs = es->GetExeFS(); | ||
| 763 | if (exefs == nullptr) | ||
| 764 | return; | ||
| 765 | |||
| 766 | const auto main = exefs->GetFile("main"); | ||
| 767 | if (main == nullptr) | ||
| 768 | return; | ||
| 769 | |||
| 770 | const auto bytes = main->ReadAllBytes(); | ||
| 771 | |||
| 772 | using namespace Common; | ||
| 773 | const auto eticket_kek = FindKeyFromHex(bytes, eticket_source_hashes[0]); | ||
| 774 | const auto eticket_kekek = FindKeyFromHex(bytes, eticket_source_hashes[1]); | ||
| 775 | |||
| 776 | const auto seed3 = data.GetRSAKekSeed3(); | ||
| 777 | const auto mask0 = data.GetRSAKekMask0(); | ||
| 778 | |||
| 779 | if (eticket_kek != Key128{}) | ||
| 780 | SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek)); | ||
| 781 | if (eticket_kekek != Key128{}) | ||
| 782 | SetKey(S128KeyType::Source, eticket_kekek, | ||
| 783 | static_cast<size_t>(SourceKeyType::ETicketKekek)); | ||
| 784 | if (seed3 != Key128{}) | ||
| 785 | SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3)); | ||
| 786 | if (mask0 != Key128{}) | ||
| 787 | SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0)); | ||
| 788 | |||
| 789 | if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} || | ||
| 790 | mask0 == Key128{}) | ||
| 791 | return; | ||
| 792 | |||
| 793 | Key128 rsa_oaep_kek{}; | ||
| 794 | for (size_t i = 0; i < rsa_oaep_kek.size(); ++i) | ||
| 795 | rsa_oaep_kek[i] = seed3[i] ^ mask0[i]; | ||
| 796 | |||
| 797 | if (rsa_oaep_kek == Key128{}) | ||
| 798 | return; | ||
| 799 | |||
| 800 | SetKey(S128KeyType::Source, rsa_oaep_kek, | ||
| 801 | static_cast<u64>(SourceKeyType::RSAOaepKekGeneration)); | ||
| 802 | |||
| 803 | Key128 temp_kek{}; | ||
| 804 | Key128 temp_kekek{}; | ||
| 805 | Key128 eticket_final{}; | ||
| 806 | |||
| 807 | // Derive ETicket RSA Kek | ||
| 808 | AESCipher<Key128> es_master(GetKey(S128KeyType::Master), Mode::ECB); | ||
| 809 | es_master.Transcode(rsa_oaep_kek.data(), rsa_oaep_kek.size(), temp_kek.data(), Op::Decrypt); | ||
| 810 | AESCipher<Key128> es_kekek(temp_kek, Mode::ECB); | ||
| 811 | es_kekek.Transcode(eticket_kekek.data(), eticket_kekek.size(), temp_kekek.data(), Op::Decrypt); | ||
| 812 | AESCipher<Key128> es_kek(temp_kekek, Mode::ECB); | ||
| 813 | es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt); | ||
| 814 | |||
| 815 | if (eticket_final == Key128{}) | ||
| 816 | return; | ||
| 817 | |||
| 818 | SetKey(S128KeyType::ETicketRSAKek, eticket_final); | ||
| 819 | |||
| 820 | // Titlekeys | ||
| 821 | data.DecryptProdInfo(GetKey(S128KeyType::BIS), | ||
| 822 | GetKey(S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak))); | ||
| 823 | |||
| 824 | const auto eticket_extended_kek = data.GetETicketExtendedKek(); | ||
| 825 | |||
| 826 | std::vector<u8> extended_iv(0x10); | ||
| 827 | std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); | ||
| 828 | std::array<u8, 0x230> extended_dec{}; | ||
| 829 | AESCipher<Key128> rsa_1(eticket_final, Mode::CTR); | ||
| 830 | rsa_1.SetIV(extended_iv); | ||
| 831 | rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, | ||
| 832 | extended_dec.data(), Op::Decrypt); | ||
| 833 | |||
| 834 | RSAKeyPair<2048> rsa_key{}; | ||
| 835 | std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); | ||
| 836 | std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); | ||
| 837 | std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); | ||
| 838 | |||
| 839 | const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + | ||
| 840 | "/system/save/80000000000000e1", | ||
| 841 | "rb+"); | ||
| 842 | const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + | ||
| 843 | "/system/save/80000000000000e2", | ||
| 844 | "rb+"); | ||
| 845 | |||
| 846 | auto res = GetTicketblob(save1); | ||
| 847 | const auto res2 = GetTicketblob(save2); | ||
| 848 | std::copy(res2.begin(), res2.end(), std::back_inserter(res)); | ||
| 849 | |||
| 850 | for (const auto& raw : res) { | ||
| 851 | const auto pair = ParseTicket(raw, rsa_key); | ||
| 852 | if (pair == boost::none) | ||
| 853 | continue; | ||
| 854 | auto [rid, key] = pair.value(); | ||
| 855 | u128 rights_id{}; | ||
| 856 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | ||
| 857 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 858 | } | ||
| 859 | } | ||
| 860 | |||
| 612 | void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { | 861 | void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { |
| 613 | if (key == Key128{}) | 862 | if (key == Key128{}) |
| 614 | return; | 863 | return; |
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 8de65ec4e..58afcdcac 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h | |||
| @@ -5,11 +5,18 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <map> | ||
| 8 | #include <string> | 9 | #include <string> |
| 9 | #include <boost/container/flat_map.hpp> | 10 | #include <boost/container/flat_map.hpp> |
| 10 | #include <boost/optional.hpp> | 11 | #include <boost/optional.hpp> |
| 11 | #include <fmt/format.h> | 12 | #include <fmt/format.h> |
| 12 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 14 | #include "core/file_sys/vfs_types.h" | ||
| 15 | #include "partition_data_manager.h" | ||
| 16 | |||
| 17 | namespace FileUtil { | ||
| 18 | class IOFile; | ||
| 19 | } | ||
| 13 | 20 | ||
| 14 | namespace Loader { | 21 | namespace Loader { |
| 15 | enum class ResultStatus : u16; | 22 | enum class ResultStatus : u16; |
| @@ -22,9 +29,18 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; | |||
| 22 | using Key128 = std::array<u8, 0x10>; | 29 | using Key128 = std::array<u8, 0x10>; |
| 23 | using Key256 = std::array<u8, 0x20>; | 30 | using Key256 = std::array<u8, 0x20>; |
| 24 | using SHA256Hash = std::array<u8, 0x20>; | 31 | using SHA256Hash = std::array<u8, 0x20>; |
| 32 | using TicketRaw = std::array<u8, 0x400>; | ||
| 25 | 33 | ||
| 26 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); | 34 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); |
| 27 | static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big."); | 35 | static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); |
| 36 | |||
| 37 | template <size_t bit_size, size_t byte_size = (bit_size >> 3)> | ||
| 38 | struct RSAKeyPair { | ||
| 39 | std::array<u8, byte_size> encryption_key; | ||
| 40 | std::array<u8, byte_size> decryption_key; | ||
| 41 | std::array<u8, byte_size> modulus; | ||
| 42 | std::array<u8, 4> exponent; | ||
| 43 | }; | ||
| 28 | 44 | ||
| 29 | enum class KeyCategory : u8 { | 45 | enum class KeyCategory : u8 { |
| 30 | Standard, | 46 | Standard, |
| @@ -140,6 +156,8 @@ public: | |||
| 140 | 156 | ||
| 141 | bool BaseDeriveNecessary(); | 157 | bool BaseDeriveNecessary(); |
| 142 | void DeriveBase(); | 158 | void DeriveBase(); |
| 159 | void DeriveETicket(PartitionDataManager data); | ||
| 160 | |||
| 143 | private: | 161 | private: |
| 144 | std::map<KeyIndex<S128KeyType>, Key128> s128_keys; | 162 | std::map<KeyIndex<S128KeyType>, Key128> s128_keys; |
| 145 | std::map<KeyIndex<S256KeyType>, Key256> s256_keys; | 163 | std::map<KeyIndex<S256KeyType>, Key256> s256_keys; |
| @@ -166,6 +184,13 @@ Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, K | |||
| 166 | Key128 DeriveKeyblobKey(Key128 sbk, Key128 tsec, Key128 source); | 184 | Key128 DeriveKeyblobKey(Key128 sbk, Key128 tsec, Key128 source); |
| 167 | 185 | ||
| 168 | boost::optional<Key128> DeriveSDSeed(); | 186 | boost::optional<Key128> DeriveSDSeed(); |
| 169 | Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys); | 187 | Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); |
| 188 | |||
| 189 | std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save); | ||
| 190 | |||
| 191 | // Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset | ||
| 192 | // 0x140-0x144 is zero) | ||
| 193 | boost::optional<std::pair<Key128, Key128>> ParseTicket( | ||
| 194 | const TicketRaw& ticket, const RSAKeyPair<2048>& eticket_extended_key); | ||
| 170 | 195 | ||
| 171 | } // namespace Core::Crypto | 196 | } // namespace Core::Crypto |