// Copyright 2018 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 "common/string_util.h" #include "core/core.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; constexpr u64 FULL_TICKET_SIZE = 0x400; using namespace Common; const std::array eticket_source_hashes{ "B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source "E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source }; const std::map, 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(SourceKeyType::Keyblob)}, "keyblob_key_source_"}, {{S128KeyType::Keyblob, 0}, "keyblob_key_"}, {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, }; namespace { template bool IsAllZeroArray(const std::array& array) { return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; }); } } // namespace u64 GetSignatureTypeDataSize(SignatureType type) { switch (type) { case SignatureType::RSA_4096_SHA1: case SignatureType::RSA_4096_SHA256: return 0x200; case SignatureType::RSA_2048_SHA1: case SignatureType::RSA_2048_SHA256: return 0x100; case SignatureType::ECDSA_SHA1: case SignatureType::ECDSA_SHA256: return 0x3C; } UNREACHABLE(); } u64 GetSignatureTypePaddingSize(SignatureType type) { switch (type) { case SignatureType::RSA_4096_SHA1: case SignatureType::RSA_4096_SHA256: case SignatureType::RSA_2048_SHA1: case SignatureType::RSA_2048_SHA256: return 0x3C; case SignatureType::ECDSA_SHA1: case SignatureType::ECDSA_SHA256: return 0x40; } UNREACHABLE(); } SignatureType Ticket::GetSignatureType() const { if (auto ticket = std::get_if(&data)) { return ticket->sig_type; } if (auto ticket = std::get_if(&data)) { return ticket->sig_type; } if (auto ticket = std::get_if(&data)) { return ticket->sig_type; } UNREACHABLE(); } TicketData& Ticket::GetData() { if (auto ticket = std::get_if(&data)) { return ticket->data; } if (auto ticket = std::get_if(&data)) { return ticket->data; } if (auto ticket = std::get_if(&data)) { return ticket->data; } UNREACHABLE(); } const TicketData& Ticket::GetData() const { if (auto ticket = std::get_if(&data)) { return ticket->data; } if (auto ticket = std::get_if(&data)) { return ticket->data; } if (auto ticket = std::get_if(&data)) { return ticket->data; } UNREACHABLE(); } u64 Ticket::GetSize() const { const auto sig_type = GetSignatureType(); return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) + GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData); } Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array& rights_id) { RSA2048Ticket out{}; out.sig_type = SignatureType::RSA_2048_SHA256; out.data.rights_id = rights_id; out.data.title_key_common = title_key; return Ticket{out}; } Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { Key128 out{}; AESCipher cipher1(master, Mode::ECB); cipher1.Transcode(kek_seed.data(), kek_seed.size(), out.data(), Op::Decrypt); AESCipher cipher2(out, Mode::ECB); cipher2.Transcode(source.data(), source.size(), out.data(), Op::Decrypt); if (key_seed != Key128{}) { AESCipher cipher3(out, Mode::ECB); cipher3.Transcode(key_seed.data(), key_seed.size(), out.data(), Op::Decrypt); } return out; } Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source) { AESCipher sbk_cipher(sbk, Mode::ECB); AESCipher 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& keyblob, const Key128& master_source) { Key128 master_root; std::memcpy(master_root.data(), keyblob.data(), sizeof(Key128)); AESCipher 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 DecryptKeyblob(const std::array& encrypted_keyblob, const Key128& key) { std::array keyblob; AESCipher cipher(key, Mode::CTR); cipher.SetIV(std::vector(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(std::size_t crypto_revision) { const auto kek_generation_source = GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration)); const auto key_generation_source = GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration)); if (HasKey(S128KeyType::Master, crypto_revision)) { for (auto kak_type : {KeyAreaKeyType::Application, KeyAreaKeyType::Ocean, KeyAreaKeyType::System}) { if (HasKey(S128KeyType::Source, static_cast(SourceKeyType::KeyAreaKey), static_cast(kak_type))) { const auto source = GetKey(S128KeyType::Source, static_cast(SourceKeyType::KeyAreaKey), static_cast(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(kak_type)); } } AESCipher master_cipher(GetKey(S128KeyType::Master, crypto_revision), Mode::ECB); for (auto key_type : {SourceKeyType::Titlekek, SourceKeyType::Package2}) { if (HasKey(S128KeyType::Source, static_cast(key_type))) { Key128 key{}; master_cipher.Transcode( GetKey(S128KeyType::Source, static_cast(key_type)).data(), key.size(), key.data(), Op::Decrypt); SetKey(key_type == SourceKeyType::Titlekek ? S128KeyType::Titlekek : S128KeyType::Package2, key, crypto_revision); } } } } RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) return {}; const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); std::vector extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10); std::array extended_dec{}; AESCipher 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()); return rsa_key; } Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { AESCipher 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; } std::optional DeriveSDSeed() { const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000043", "rb+"); if (!save_43.IsOpen()) return {}; const FileUtil::IOFile sd_private( FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+"); if (!sd_private.IsOpen()) return {}; std::array private_seed{}; if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) { return {}; } std::array buffer{}; std::size_t offset = 0; for (; offset + 0x10 < save_43.GetSize(); ++offset) { if (!save_43.Seek(offset, SEEK_SET)) { return {}; } save_43.ReadBytes(buffer.data(), buffer.size()); if (buffer == private_seed) { break; } } if (!save_43.Seek(offset + 0x10, SEEK_SET)) { return {}; } Key128 seed{}; if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) { return {}; } return seed; } Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, KeyManager& keys) { if (!keys.HasKey(S128KeyType::Source, static_cast(SourceKeyType::SDKek))) return Loader::ResultStatus::ErrorMissingSDKEKSource; if (!keys.HasKey(S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration))) return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource; if (!keys.HasKey(S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration))) return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource; const auto sd_kek_source = keys.GetKey(S128KeyType::Source, static_cast(SourceKeyType::SDKek)); const auto aes_kek_gen = keys.GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration)); const auto aes_key_gen = keys.GetKey(S128KeyType::Source, static_cast(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; const auto sd_seed = keys.GetKey(S128KeyType::SDSeed); if (!keys.HasKey(S256KeyType::SDKeySource, static_cast(SDKeyType::Save))) return Loader::ResultStatus::ErrorMissingSDSaveKeySource; if (!keys.HasKey(S256KeyType::SDKeySource, static_cast(SDKeyType::NCA))) return Loader::ResultStatus::ErrorMissingSDNCAKeySource; std::array sd_key_sources{ keys.GetKey(S256KeyType::SDKeySource, static_cast(SDKeyType::Save)), keys.GetKey(S256KeyType::SDKeySource, static_cast(SDKeyType::NCA)), }; // Combine sources and seed for (auto& source : sd_key_sources) { for (std::size_t i = 0; i < source.size(); ++i) source[i] ^= sd_seed[i & 0xF]; } AESCipher cipher(sd_kek, Mode::ECB); // The transform manipulates sd_keys as part of the Transcode, so the return/output is // unnecessary. This does not alter sd_keys_sources. std::transform(sd_key_sources.begin(), sd_key_sources.end(), sd_keys.begin(), sd_key_sources.begin(), [&cipher](const Key256& source, Key256& out) { cipher.Transcode(source.data(), source.size(), out.data(), Op::Decrypt); return source; ///< Return unaltered source to satisfy output requirement. }); keys.SetKey(S256KeyType::SDKey, sd_keys[0], static_cast(SDKeyType::Save)); keys.SetKey(S256KeyType::SDKey, sd_keys[1], static_cast(SDKeyType::NCA)); return Loader::ResultStatus::Success; } std::vector GetTicketblob(const FileUtil::IOFile& ticket_save) { if (!ticket_save.IsOpen()) return {}; std::vector buffer(ticket_save.GetSize()); if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) { return {}; } std::vector out; 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(Ticket)); offset += FULL_TICKET_SIZE; } } return out; } template static std::array operator^(const std::array& lhs, const std::array& rhs) { std::array out; std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(), [](u8 lhs, u8 rhs) { return u8(lhs ^ rhs); }); return out; } template static std::array MGF1(const std::array& seed) { // Avoids truncation overflow within the loop below. static_assert(target_size <= 0xFF); std::array seed_exp{}; std::memcpy(seed_exp.data(), seed.data(), in_size); std::vector out; size_t i = 0; while (out.size() < target_size) { out.resize(out.size() + 0x20); seed_exp[in_size + 3] = static_cast(i); mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0); ++i; } std::array target; std::memcpy(target.data(), out.data(), target_size); return target; } template static std::optional FindTicketOffset(const std::array& 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 {}; } } return offset; } std::optional> ParseTicket(const Ticket& ticket, const RSAKeyPair<2048>& key) { const auto issuer = ticket.GetData().issuer; if (IsAllZeroArray(issuer)) return {}; if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); } Key128 rights_id = ticket.GetData().rights_id; if (rights_id == Key128{}) return {}; if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { return std::make_pair(rights_id, ticket.GetData().title_key_common); } 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.GetData().title_key_block.data(), 0x100); mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); std::array rsa_step; mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size()); u8 m_0 = rsa_step[0]; std::array m_1; std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size()); std::array m_2; std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size()); if (m_0 != 0) return {}; m_1 = m_1 ^ MGF1<0x20>(m_2); m_2 = m_2 ^ MGF1<0xDF>(m_1); const auto offset = FindTicketOffset(m_2); if (!offset) return {}; ASSERT(*offset > 0); Key128 key_temp{}; std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); return std::make_pair(rights_id, key_temp); } KeyManager::KeyManager() { // Initialize keys const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); if (Settings::values.use_dev_keys) { dev_mode = true; AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "dev.keys_autogenerated", false); } else { dev_mode = false; AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false); AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "prod.keys_autogenerated", false); } 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::isxdigit(c); }); } void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { std::ifstream file; OpenFStream(file, filename, std::ios_base::in); if (!file.is_open()) return; std::string line; while (std::getline(file, line)) { std::vector out; std::stringstream stream(line); std::string item; while (std::getline(stream, item, '=')) out.push_back(std::move(item)); if (out.size() != 2) continue; 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{}; std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); Key128 key = Common::HexStringToArray<16>(out[1]); s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key; } else { out[0] = Common::ToLower(out[0]); if (s128_file_id.find(out[0]) != s128_file_id.end()) { const auto index = s128_file_id.at(out[0]); Key128 key = Common::HexStringToArray<16>(out[1]); s128_keys[{index.type, index.field1, index.field2}] = key; } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { 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 if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { eticket_extended_kek = Common::HexStringToArray<576>(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 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]); } } } } } } void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, const std::string& filename, bool title) { if (FileUtil::Exists(dir1 + DIR_SEP + filename)) LoadFromFile(dir1 + DIR_SEP + filename, title); else if (FileUtil::Exists(dir2 + DIR_SEP + filename)) 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(KeyAreaKeyType::Application)) || check_key_existence(S128KeyType::KeyArea, i, static_cast(KeyAreaKeyType::Ocean)) || check_key_existence(S128KeyType::KeyArea, i, static_cast(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(); } bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const { return s256_keys.find({id, field1, field2}) != s256_keys.end(); } Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const { if (!HasKey(id, field1, field2)) return {}; return s128_keys.at({id, field1, field2}); } Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { if (!HasKey(id, field1, field2)) return {}; return s256_keys.at({id, field1, field2}); } 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(bis_type))) { std::memcpy( out.data() + sizeof(Key128) * static_cast(bis_type), s128_keys.at({S128KeyType::BIS, partition_id, static_cast(bis_type)}).data(), sizeof(Key128)); } } return out; } template void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, const std::array& key) { const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); std::string filename = "title.keys_autogenerated"; 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); if (!file.is_open()) return; if (add_info_text) { file << "# This file is autogenerated by Yuzu\n" << "# It serves to store keys that were automatically generated from the normal keys\n" << "# If you are experiencing issues involving keys, it may help to delete this file\n"; } file << fmt::format("\n{} = {}", keyname, Common::HexToString(key)); AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title); } void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { if (s128_keys.find({id, field1, field2}) != s128_keys.end()) return; if (id == S128KeyType::Titlekey) { Key128 rights_id; std::memcpy(rights_id.data(), &field2, sizeof(u64)); std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64)); WriteKeyToFile(KeyCategory::Title, Common::HexToString(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> elem) { return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == std::tie(id, field1, field2); }); if (iter2 != s128_file_id.end()) WriteKeyToFile(category, iter2->first, key); // Variable cases if (id == S128KeyType::KeyArea) { static constexpr std::array 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(SourceKeyType::Keyblob)) { WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key); } s128_keys[{id, field1, field2}] = key; } void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { if (s256_keys.find({id, field1, field2}) != s256_keys.end()) return; const auto iter = std::find_if( s256_file_id.begin(), s256_file_id.end(), [&id, &field1, &field2](const std::pair> elem) { return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == std::tie(id, field1, field2); }); if (iter != s256_file_id.end()) WriteKeyToFile(KeyCategory::Standard, iter->first, key); s256_keys[{id, field1, field2}] = key; } bool KeyManager::KeyFileExists(bool title) { const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); if (title) { return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys"); } if (Settings::values.use_dev_keys) { return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys"); } return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); } void KeyManager::DeriveSDSeedLazy() { if (HasKey(S128KeyType::SDSeed)) return; const auto res = DeriveSDSeed(); if (res) SetKey(S128KeyType::SDSeed, *res); } 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(BISKeyType::Crypto)) && HasKey(S128KeyType::BIS, id, static_cast(BISKeyType::Tweak)); }; const auto copy_bis = [this](u64 id_from, u64 id_to) { SetKey(S128KeyType::BIS, GetKey(S128KeyType::BIS, id_from, static_cast(BISKeyType::Crypto)), id_to, static_cast(BISKeyType::Crypto)); SetKey(S128KeyType::BIS, GetKey(S128KeyType::BIS, id_from, static_cast(BISKeyType::Tweak)), id_to, static_cast(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(SourceKeyType::Keyblob), i) || encrypted_keyblobs[i] == std::array{}) { revisions.reset(i); } } if (!revisions.any()) return; const auto sbk = GetKey(S128KeyType::SecureBoot); const auto tsec = GetKey(S128KeyType::TSEC); 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(SourceKeyType::Keyblob), i)); SetKey(S128KeyType::Keyblob, key, i); // Derive keyblob MAC key if (!HasKey(S128KeyType::Source, static_cast(SourceKeyType::KeyblobMAC))) continue; const auto mac_key = DeriveKeyblobMACKey( key, GetKey(S128KeyType::Source, static_cast(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{}) { 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(SourceKeyType::Master))) { SetKey(S128KeyType::Master, DeriveMasterKey(keyblobs[i], GetKey(S128KeyType::Source, static_cast(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(SourceKeyType::AESKeyGeneration)) && HasKey(S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration)) && HasKey(S128KeyType::Source, static_cast(SourceKeyType::HeaderKek)) && HasKey(S256KeyType::HeaderSource)) { const auto header_kek = GenerateKeyEncryptionKey( GetKey(S128KeyType::Source, static_cast(SourceKeyType::HeaderKek)), GetKey(S128KeyType::Master, 0), GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration)), GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration))); SetKey(S128KeyType::HeaderKek, header_kek); AESCipher 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 = Core::System::GetInstance().GetContentProvider().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(SourceKeyType::ETicketKek)); if (eticket_kekek != Key128{}) { SetKey(S128KeyType::Source, eticket_kekek, static_cast(SourceKeyType::ETicketKekek)); } if (seed3 != Key128{}) SetKey(S128KeyType::RSAKek, seed3, static_cast(RSAKekType::Seed3)); if (mask0 != Key128{}) SetKey(S128KeyType::RSAKek, mask0, static_cast(RSAKekType::Mask0)); if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} || mask0 == Key128{}) { return; } const Key128 rsa_oaep_kek = seed3 ^ mask0; if (rsa_oaep_kek == Key128{}) { return; } SetKey(S128KeyType::Source, rsa_oaep_kek, static_cast(SourceKeyType::RSAOaepKekGeneration)); Key128 temp_kek{}; Key128 temp_kekek{}; Key128 eticket_final{}; // Derive ETicket RSA Kek AESCipher es_master(GetKey(S128KeyType::Master), Mode::ECB); es_master.Transcode(rsa_oaep_kek.data(), rsa_oaep_kek.size(), temp_kek.data(), Op::Decrypt); AESCipher es_kekek(temp_kek, Mode::ECB); es_kekek.Transcode(eticket_kekek.data(), eticket_kekek.size(), temp_kekek.data(), Op::Decrypt); AESCipher 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)); eticket_extended_kek = data.GetETicketExtendedKek(); WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); PopulateTickets(); } void KeyManager::PopulateTickets() { const auto rsa_key = GetETicketRSAKey(); if (rsa_key == RSAKeyPair<2048>{}) return; if (!common_tickets.empty() && !personal_tickets.empty()) return; 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+"); const auto blob2 = GetTicketblob(save2); auto res = GetTicketblob(save1); const auto idx = res.size(); res.insert(res.end(), blob2.begin(), blob2.end()); for (std::size_t i = 0; i < res.size(); ++i) { const auto common = i < idx; const auto pair = ParseTicket(res[i], rsa_key); if (!pair) continue; const auto& [rid, key] = *pair; u128 rights_id; std::memcpy(rights_id.data(), rid.data(), rid.size()); if (common) { common_tickets[rights_id] = res[i]; } else { personal_tickets[rights_id] = res[i]; } SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); } } void KeyManager::SynthesizeTickets() { for (const auto& key : s128_keys) { if (key.first.type != S128KeyType::Titlekey) { continue; } u128 rights_id{key.first.field1, key.first.field2}; Key128 rights_id_2; std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); common_tickets.insert_or_assign(rights_id, ticket); } } 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{}) 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(SourceKeyType::Package2)); SetKeyWrapped(S128KeyType::Source, data.GetAESKekGenerationSource(), static_cast(SourceKeyType::AESKekGeneration)); SetKeyWrapped(S128KeyType::Source, data.GetTitlekekSource(), static_cast(SourceKeyType::Titlekek)); SetKeyWrapped(S128KeyType::Source, data.GetMasterKeySource(), static_cast(SourceKeyType::Master)); SetKeyWrapped(S128KeyType::Source, data.GetKeyblobMACKeySource(), static_cast(SourceKeyType::KeyblobMAC)); for (size_t i = 0; i < PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH; ++i) { SetKeyWrapped(S128KeyType::Source, data.GetKeyblobKeySource(i), static_cast(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(i)) != Key128{}) { latest_master = GetKey(S128KeyType::Master, static_cast(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 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(SourceKeyType::KeyAreaKey), static_cast(KeyAreaKeyType::Application)); SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyOceanSource(), static_cast(SourceKeyType::KeyAreaKey), static_cast(KeyAreaKeyType::Ocean)); SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeySystemSource(), static_cast(SourceKeyType::KeyAreaKey), static_cast(KeyAreaKeyType::System)); SetKeyWrapped(S128KeyType::Source, data.GetSDKekSource(), static_cast(SourceKeyType::SDKek)); SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDSaveKeySource(), static_cast(SDKeyType::Save)); SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDNCAKeySource(), static_cast(SDKeyType::NCA)); SetKeyWrapped(S128KeyType::Source, data.GetHeaderKekSource(), static_cast(SourceKeyType::HeaderKek)); SetKeyWrapped(S256KeyType::HeaderSource, data.GetHeaderKeySource()); SetKeyWrapped(S128KeyType::Source, data.GetAESKeyGenerationSource(), static_cast(SourceKeyType::AESKeyGeneration)); DeriveBase(); } const std::map& KeyManager::GetCommonTickets() const { return common_tickets; } const std::map& KeyManager::GetPersonalizedTickets() const { return personal_tickets; } bool KeyManager::AddTicketCommon(Ticket raw) { const auto rsa_key = GetETicketRSAKey(); if (rsa_key == RSAKeyPair<2048>{}) return false; const auto pair = ParseTicket(raw, rsa_key); if (!pair) return false; const auto& [rid, key] = *pair; u128 rights_id; std::memcpy(rights_id.data(), rid.data(), rid.size()); common_tickets[rights_id] = raw; SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); return true; } bool KeyManager::AddTicketPersonalized(Ticket raw) { const auto rsa_key = GetETicketRSAKey(); if (rsa_key == RSAKeyPair<2048>{}) return false; const auto pair = ParseTicket(raw, rsa_key); if (!pair) return false; const auto& [rid, key] = *pair; u128 rights_id; std::memcpy(rights_id.data(), rid.data(), rid.size()); common_tickets[rights_id] = raw; SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); return true; } const boost::container::flat_map> KeyManager::s128_file_id = { {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, {"eticket_rsa_kek_source", {S128KeyType::Source, static_cast(SourceKeyType::ETicketKek), 0}}, {"eticket_rsa_kekek_source", {S128KeyType::Source, static_cast(SourceKeyType::ETicketKekek), 0}}, {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast(RSAKekType::Mask0), 0}}, {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast(RSAKekType::Seed3), 0}}, {"rsa_oaep_kek_generation_source", {S128KeyType::Source, static_cast(SourceKeyType::RSAOaepKekGeneration), 0}}, {"sd_card_kek_source", {S128KeyType::Source, static_cast(SourceKeyType::SDKek), 0}}, {"aes_kek_generation_source", {S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration), 0}}, {"aes_key_generation_source", {S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration), 0}}, {"package2_key_source", {S128KeyType::Source, static_cast(SourceKeyType::Package2), 0}}, {"master_key_source", {S128KeyType::Source, static_cast(SourceKeyType::Master), 0}}, {"header_kek_source", {S128KeyType::Source, static_cast(SourceKeyType::HeaderKek), 0}}, {"key_area_key_application_source", {S128KeyType::Source, static_cast(SourceKeyType::KeyAreaKey), static_cast(KeyAreaKeyType::Application)}}, {"key_area_key_ocean_source", {S128KeyType::Source, static_cast(SourceKeyType::KeyAreaKey), static_cast(KeyAreaKeyType::Ocean)}}, {"key_area_key_system_source", {S128KeyType::Source, static_cast(SourceKeyType::KeyAreaKey), static_cast(KeyAreaKeyType::System)}}, {"titlekek_source", {S128KeyType::Source, static_cast(SourceKeyType::Titlekek), 0}}, {"keyblob_mac_key_source", {S128KeyType::Source, static_cast(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(BISKeyType::Crypto)}}, {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast(BISKeyType::Tweak)}}, {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast(BISKeyType::Crypto)}}, {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast(BISKeyType::Tweak)}}, {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast(BISKeyType::Crypto)}}, {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast(BISKeyType::Tweak)}}, {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast(BISKeyType::Crypto)}}, {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast(BISKeyType::Tweak)}}, {"header_kek", {S128KeyType::HeaderKek, 0, 0}}, {"sd_card_kek", {S128KeyType::SDKek, 0, 0}}, }; const boost::container::flat_map> KeyManager::s256_file_id = { {"header_key", {S256KeyType::Header, 0, 0}}, {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast(SDKeyType::Save), 0}}, {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast(SDKeyType::NCA), 0}}, {"header_key_source", {S256KeyType::HeaderSource, 0, 0}}, {"sd_card_save_key", {S256KeyType::SDKey, static_cast(SDKeyType::Save), 0}}, {"sd_card_nca_key", {S256KeyType::SDKey, static_cast(SDKeyType::NCA), 0}}, }; } // namespace Core::Crypto