|
|
|
@ -96,13 +96,13 @@ u64 GetSignatureTypePaddingSize(SignatureType type) { |
|
|
|
} |
|
|
|
|
|
|
|
SignatureType Ticket::GetSignatureType() const { |
|
|
|
if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { |
|
|
|
if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { |
|
|
|
return ticket->sig_type; |
|
|
|
} |
|
|
|
if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { |
|
|
|
if (const auto* ticket = std::get_if<RSA2048Ticket>(&data)) { |
|
|
|
return ticket->sig_type; |
|
|
|
} |
|
|
|
if (auto ticket = std::get_if<ECDSATicket>(&data)) { |
|
|
|
if (const auto* ticket = std::get_if<ECDSATicket>(&data)) { |
|
|
|
return ticket->sig_type; |
|
|
|
} |
|
|
|
|
|
|
|
@ -110,13 +110,13 @@ SignatureType Ticket::GetSignatureType() const { |
|
|
|
} |
|
|
|
|
|
|
|
TicketData& Ticket::GetData() { |
|
|
|
if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { |
|
|
|
if (auto* ticket = std::get_if<RSA4096Ticket>(&data)) { |
|
|
|
return ticket->data; |
|
|
|
} |
|
|
|
if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { |
|
|
|
if (auto* ticket = std::get_if<RSA2048Ticket>(&data)) { |
|
|
|
return ticket->data; |
|
|
|
} |
|
|
|
if (auto ticket = std::get_if<ECDSATicket>(&data)) { |
|
|
|
if (auto* ticket = std::get_if<ECDSATicket>(&data)) { |
|
|
|
return ticket->data; |
|
|
|
} |
|
|
|
|
|
|
|
@ -124,13 +124,13 @@ TicketData& Ticket::GetData() { |
|
|
|
} |
|
|
|
|
|
|
|
const TicketData& Ticket::GetData() const { |
|
|
|
if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { |
|
|
|
if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { |
|
|
|
return ticket->data; |
|
|
|
} |
|
|
|
if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { |
|
|
|
if (const auto* ticket = std::get_if<RSA2048Ticket>(&data)) { |
|
|
|
return ticket->data; |
|
|
|
} |
|
|
|
if (auto ticket = std::get_if<ECDSATicket>(&data)) { |
|
|
|
if (const auto* ticket = std::get_if<ECDSATicket>(&data)) { |
|
|
|
return ticket->data; |
|
|
|
} |
|
|
|
|
|
|
|
@ -233,8 +233,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { |
|
|
|
} |
|
|
|
|
|
|
|
RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { |
|
|
|
if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) |
|
|
|
if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { |
|
|
|
return {}; |
|
|
|
} |
|
|
|
|
|
|
|
const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); |
|
|
|
|
|
|
|
@ -261,27 +262,30 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) |
|
|
|
} |
|
|
|
|
|
|
|
std::optional<Key128> DeriveSDSeed() { |
|
|
|
const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + |
|
|
|
"/system/save/8000000000000043", |
|
|
|
"rb+"); |
|
|
|
if (!save_43.IsOpen()) |
|
|
|
return {}; |
|
|
|
const Common::FS::IOFile save_43(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + |
|
|
|
"/system/save/8000000000000043", |
|
|
|
"rb+"); |
|
|
|
if (!save_43.IsOpen()) { |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
const FileUtil::IOFile sd_private( |
|
|
|
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+"); |
|
|
|
if (!sd_private.IsOpen()) |
|
|
|
return {}; |
|
|
|
const Common::FS::IOFile sd_private(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) + |
|
|
|
"/Nintendo/Contents/private", |
|
|
|
"rb+"); |
|
|
|
if (!sd_private.IsOpen()) { |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
std::array<u8, 0x10> private_seed{}; |
|
|
|
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) { |
|
|
|
return {}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
std::array<u8, 0x10> buffer{}; |
|
|
|
std::size_t offset = 0; |
|
|
|
for (; offset + 0x10 < save_43.GetSize(); ++offset) { |
|
|
|
if (!save_43.Seek(offset, SEEK_SET)) { |
|
|
|
return {}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
save_43.ReadBytes(buffer.data(), buffer.size()); |
|
|
|
@ -291,23 +295,26 @@ std::optional<Key128> DeriveSDSeed() { |
|
|
|
} |
|
|
|
|
|
|
|
if (!save_43.Seek(offset + 0x10, SEEK_SET)) { |
|
|
|
return {}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
Key128 seed{}; |
|
|
|
if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) { |
|
|
|
return {}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
return seed; |
|
|
|
} |
|
|
|
|
|
|
|
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) { |
|
|
|
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek))) |
|
|
|
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))) |
|
|
|
} |
|
|
|
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)); |
|
|
|
@ -320,14 +327,17 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke |
|
|
|
GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen); |
|
|
|
keys.SetKey(S128KeyType::SDKek, sd_kek); |
|
|
|
|
|
|
|
if (!keys.HasKey(S128KeyType::SDSeed)) |
|
|
|
if (!keys.HasKey(S128KeyType::SDSeed)) { |
|
|
|
return Loader::ResultStatus::ErrorMissingSDSeed; |
|
|
|
} |
|
|
|
const auto sd_seed = keys.GetKey(S128KeyType::SDSeed); |
|
|
|
|
|
|
|
if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save))) |
|
|
|
if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save))) { |
|
|
|
return Loader::ResultStatus::ErrorMissingSDSaveKeySource; |
|
|
|
if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA))) |
|
|
|
} |
|
|
|
if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA))) { |
|
|
|
return Loader::ResultStatus::ErrorMissingSDNCAKeySource; |
|
|
|
} |
|
|
|
|
|
|
|
std::array<Key256, 2> sd_key_sources{ |
|
|
|
keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)), |
|
|
|
@ -336,8 +346,9 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke |
|
|
|
|
|
|
|
// Combine sources and seed
|
|
|
|
for (auto& source : sd_key_sources) { |
|
|
|
for (std::size_t i = 0; i < source.size(); ++i) |
|
|
|
for (std::size_t i = 0; i < source.size(); ++i) { |
|
|
|
source[i] ^= sd_seed[i & 0xF]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
AESCipher<Key128> cipher(sd_kek, Mode::ECB); |
|
|
|
@ -355,9 +366,10 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke |
|
|
|
return Loader::ResultStatus::Success; |
|
|
|
} |
|
|
|
|
|
|
|
std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) { |
|
|
|
if (!ticket_save.IsOpen()) |
|
|
|
std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) { |
|
|
|
if (!ticket_save.IsOpen()) { |
|
|
|
return {}; |
|
|
|
} |
|
|
|
|
|
|
|
std::vector<u8> buffer(ticket_save.GetSize()); |
|
|
|
if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) { |
|
|
|
@ -417,7 +429,7 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { |
|
|
|
offset = i + 1; |
|
|
|
break; |
|
|
|
} else if (data[i] != 0x0) { |
|
|
|
return {}; |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -427,16 +439,18 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { |
|
|
|
std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, |
|
|
|
const RSAKeyPair<2048>& key) { |
|
|
|
const auto issuer = ticket.GetData().issuer; |
|
|
|
if (IsAllZeroArray(issuer)) |
|
|
|
return {}; |
|
|
|
if (IsAllZeroArray(issuer)) { |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
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 (rights_id == Key128{}) { |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), |
|
|
|
ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { |
|
|
|
@ -468,15 +482,17 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, |
|
|
|
std::array<u8, 0xDF> m_2; |
|
|
|
std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size()); |
|
|
|
|
|
|
|
if (m_0 != 0) |
|
|
|
return {}; |
|
|
|
if (m_0 != 0) { |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
|
|
|
|
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 {}; |
|
|
|
if (!offset) { |
|
|
|
return std::nullopt; |
|
|
|
} |
|
|
|
ASSERT(*offset > 0); |
|
|
|
|
|
|
|
Key128 key_temp{}; |
|
|
|
@ -487,8 +503,8 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, |
|
|
|
|
|
|
|
KeyManager::KeyManager() { |
|
|
|
// Initialize keys
|
|
|
|
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); |
|
|
|
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); |
|
|
|
const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); |
|
|
|
const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); |
|
|
|
if (Settings::values.use_dev_keys) { |
|
|
|
dev_mode = true; |
|
|
|
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); |
|
|
|
@ -506,34 +522,39 @@ KeyManager::KeyManager() { |
|
|
|
} |
|
|
|
|
|
|
|
static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { |
|
|
|
if (base.size() < begin + 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()) |
|
|
|
Common::FS::OpenFStream(file, filename, std::ios_base::in); |
|
|
|
if (!file.is_open()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
std::string line; |
|
|
|
while (std::getline(file, line)) { |
|
|
|
std::vector<std::string> out; |
|
|
|
std::stringstream stream(line); |
|
|
|
std::string item; |
|
|
|
while (std::getline(stream, item, '=')) |
|
|
|
while (std::getline(stream, item, '=')) { |
|
|
|
out.push_back(std::move(item)); |
|
|
|
} |
|
|
|
|
|
|
|
if (out.size() != 2) |
|
|
|
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) |
|
|
|
if (out[0].compare(0, 1, "#") == 0) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if (is_title_keys) { |
|
|
|
auto rights_id_raw = Common::HexStringToArray<16>(out[0]); |
|
|
|
@ -553,14 +574,16 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { |
|
|
|
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)) |
|
|
|
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)) |
|
|
|
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]); |
|
|
|
@ -568,8 +591,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { |
|
|
|
eticket_extended_kek = Common::HexStringToArray<576>(out[1]); |
|
|
|
} else { |
|
|
|
for (const auto& kv : KEYS_VARIABLE_LENGTH) { |
|
|
|
if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) |
|
|
|
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); |
|
|
|
@ -604,10 +628,11 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { |
|
|
|
|
|
|
|
void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, |
|
|
|
const std::string& filename, bool title) { |
|
|
|
if (FileUtil::Exists(dir1 + DIR_SEP + filename)) |
|
|
|
if (Common::FS::Exists(dir1 + DIR_SEP + filename)) { |
|
|
|
LoadFromFile(dir1 + DIR_SEP + filename, title); |
|
|
|
else if (FileUtil::Exists(dir2 + DIR_SEP + filename)) |
|
|
|
} else if (Common::FS::Exists(dir2 + DIR_SEP + filename)) { |
|
|
|
LoadFromFile(dir2 + DIR_SEP + filename, title); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
bool KeyManager::BaseDeriveNecessary() const { |
|
|
|
@ -615,8 +640,9 @@ bool KeyManager::BaseDeriveNecessary() const { |
|
|
|
return !HasKey(key_type, index1, index2); |
|
|
|
}; |
|
|
|
|
|
|
|
if (check_key_existence(S256KeyType::Header)) |
|
|
|
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) || |
|
|
|
@ -641,14 +667,16 @@ bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const { |
|
|
|
} |
|
|
|
|
|
|
|
Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const { |
|
|
|
if (!HasKey(id, field1, field2)) |
|
|
|
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)) |
|
|
|
if (!HasKey(id, field1, field2)) { |
|
|
|
return {}; |
|
|
|
} |
|
|
|
return s256_keys.at({id, field1, field2}); |
|
|
|
} |
|
|
|
|
|
|
|
@ -670,7 +698,7 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const { |
|
|
|
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); |
|
|
|
const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); |
|
|
|
std::string filename = "title.keys_autogenerated"; |
|
|
|
if (category == KeyCategory::Standard) { |
|
|
|
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; |
|
|
|
@ -679,9 +707,9 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, |
|
|
|
} |
|
|
|
|
|
|
|
const auto path = yuzu_keys_dir + DIR_SEP + filename; |
|
|
|
const auto add_info_text = !FileUtil::Exists(path); |
|
|
|
FileUtil::CreateFullPath(path); |
|
|
|
FileUtil::IOFile file{path, "a"}; |
|
|
|
const auto add_info_text = !Common::FS::Exists(path); |
|
|
|
Common::FS::CreateFullPath(path); |
|
|
|
Common::FS::IOFile file{path, "a"}; |
|
|
|
if (!file.IsOpen()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
@ -765,29 +793,31 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { |
|
|
|
} |
|
|
|
|
|
|
|
bool KeyManager::KeyFileExists(bool title) { |
|
|
|
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); |
|
|
|
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); |
|
|
|
const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); |
|
|
|
const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); |
|
|
|
if (title) { |
|
|
|
return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || |
|
|
|
FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys"); |
|
|
|
return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || |
|
|
|
Common::FS::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 Common::FS::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || |
|
|
|
Common::FS::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"); |
|
|
|
return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || |
|
|
|
Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); |
|
|
|
} |
|
|
|
|
|
|
|
void KeyManager::DeriveSDSeedLazy() { |
|
|
|
if (HasKey(S128KeyType::SDSeed)) |
|
|
|
if (HasKey(S128KeyType::SDSeed)) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const auto res = DeriveSDSeed(); |
|
|
|
if (res) |
|
|
|
if (res) { |
|
|
|
SetKey(S128KeyType::SDSeed, *res); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) { |
|
|
|
@ -799,11 +829,13 @@ static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) { |
|
|
|
} |
|
|
|
|
|
|
|
void KeyManager::DeriveBase() { |
|
|
|
if (!BaseDeriveNecessary()) |
|
|
|
if (!BaseDeriveNecessary()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC)) |
|
|
|
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)) && |
|
|
|
@ -820,10 +852,11 @@ void KeyManager::DeriveBase() { |
|
|
|
static_cast<u64>(BISKeyType::Tweak)); |
|
|
|
}; |
|
|
|
|
|
|
|
if (has_bis(2) && !has_bis(3)) |
|
|
|
if (has_bis(2) && !has_bis(3)) { |
|
|
|
copy_bis(2, 3); |
|
|
|
else if (has_bis(3) && !has_bis(2)) |
|
|
|
} 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) { |
|
|
|
@ -833,15 +866,17 @@ void KeyManager::DeriveBase() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!revisions.any()) |
|
|
|
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]) |
|
|
|
if (!revisions[i]) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// Derive keyblob key
|
|
|
|
const auto key = DeriveKeyblobKey( |
|
|
|
@ -850,16 +885,18 @@ void KeyManager::DeriveBase() { |
|
|
|
SetKey(S128KeyType::Keyblob, key, i); |
|
|
|
|
|
|
|
// Derive keyblob MAC key
|
|
|
|
if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))) |
|
|
|
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) |
|
|
|
if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// Decrypt keyblob
|
|
|
|
if (keyblobs[i] == std::array<u8, 0x90>{}) { |
|
|
|
@ -883,16 +920,19 @@ void KeyManager::DeriveBase() { |
|
|
|
|
|
|
|
revisions.set(); |
|
|
|
for (size_t i = 0; i < revisions.size(); ++i) { |
|
|
|
if (!HasKey(S128KeyType::Master, i)) |
|
|
|
if (!HasKey(S128KeyType::Master, i)) { |
|
|
|
revisions.reset(i); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!revisions.any()) |
|
|
|
if (!revisions.any()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
for (size_t i = 0; i < revisions.size(); ++i) { |
|
|
|
if (!revisions[i]) |
|
|
|
if (!revisions[i]) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// Derive general purpose keys
|
|
|
|
DeriveGeneralPurposeKeys(i); |
|
|
|
@ -922,16 +962,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { |
|
|
|
const auto es = Core::System::GetInstance().GetContentProvider().GetEntry( |
|
|
|
0x0100000000000033, FileSys::ContentRecordType::Program); |
|
|
|
|
|
|
|
if (es == nullptr) |
|
|
|
if (es == nullptr) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const auto exefs = es->GetExeFS(); |
|
|
|
if (exefs == nullptr) |
|
|
|
if (exefs == nullptr) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const auto main = exefs->GetFile("main"); |
|
|
|
if (main == nullptr) |
|
|
|
if (main == nullptr) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const auto bytes = main->ReadAllBytes(); |
|
|
|
|
|
|
|
@ -941,16 +984,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { |
|
|
|
const auto seed3 = data.GetRSAKekSeed3(); |
|
|
|
const auto mask0 = data.GetRSAKekMask0(); |
|
|
|
|
|
|
|
if (eticket_kek != Key128{}) |
|
|
|
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{}) |
|
|
|
if (seed3 != Key128{}) { |
|
|
|
SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3)); |
|
|
|
if (mask0 != Key128{}) |
|
|
|
} |
|
|
|
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; |
|
|
|
@ -976,8 +1022,9 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { |
|
|
|
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{}) |
|
|
|
if (eticket_final == Key128{}) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
SetKey(S128KeyType::ETicketRSAKek, eticket_final); |
|
|
|
|
|
|
|
@ -992,18 +1039,20 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { |
|
|
|
void KeyManager::PopulateTickets() { |
|
|
|
const auto rsa_key = GetETicketRSAKey(); |
|
|
|
|
|
|
|
if (rsa_key == RSAKeyPair<2048>{}) |
|
|
|
if (rsa_key == RSAKeyPair<2048>{}) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (!common_tickets.empty() && !personal_tickets.empty()) |
|
|
|
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 Common::FS::IOFile save1(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + |
|
|
|
"/system/save/80000000000000e1", |
|
|
|
"rb+"); |
|
|
|
const Common::FS::IOFile save2(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + |
|
|
|
"/system/save/80000000000000e2", |
|
|
|
"rb+"); |
|
|
|
|
|
|
|
const auto blob2 = GetTicketblob(save2); |
|
|
|
auto res = GetTicketblob(save1); |
|
|
|
@ -1013,8 +1062,10 @@ void KeyManager::PopulateTickets() { |
|
|
|
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) |
|
|
|
if (!pair) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
const auto& [rid, key] = *pair; |
|
|
|
u128 rights_id; |
|
|
|
std::memcpy(rights_id.data(), rid.data(), rid.size()); |
|
|
|
@ -1043,27 +1094,33 @@ void KeyManager::SynthesizeTickets() { |
|
|
|
} |
|
|
|
|
|
|
|
void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { |
|
|
|
if (key == Key128{}) |
|
|
|
if (key == Key128{}) { |
|
|
|
return; |
|
|
|
} |
|
|
|
SetKey(id, key, field1, field2); |
|
|
|
} |
|
|
|
|
|
|
|
void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) { |
|
|
|
if (key == Key256{}) |
|
|
|
if (key == Key256{}) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
SetKey(id, key, field1, field2); |
|
|
|
} |
|
|
|
|
|
|
|
void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { |
|
|
|
if (!BaseDeriveNecessary()) |
|
|
|
if (!BaseDeriveNecessary()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (!data.HasBoot0()) |
|
|
|
if (!data.HasBoot0()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) { |
|
|
|
if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{}) |
|
|
|
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]); |
|
|
|
@ -1085,8 +1142,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { |
|
|
|
static_cast<u64>(SourceKeyType::Keyblob), i); |
|
|
|
} |
|
|
|
|
|
|
|
if (data.HasFuses()) |
|
|
|
if (data.HasFuses()) { |
|
|
|
SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey()); |
|
|
|
} |
|
|
|
|
|
|
|
DeriveBase(); |
|
|
|
|
|
|
|
@ -1100,8 +1158,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { |
|
|
|
|
|
|
|
const auto masters = data.GetTZMasterKeys(latest_master); |
|
|
|
for (size_t i = 0; i < masters.size(); ++i) { |
|
|
|
if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i)) |
|
|
|
if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i)) { |
|
|
|
SetKey(S128KeyType::Master, masters[i], i); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
DeriveBase(); |
|
|
|
@ -1111,8 +1170,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { |
|
|
|
|
|
|
|
std::array<Key128, 0x20> package2_keys{}; |
|
|
|
for (size_t i = 0; i < package2_keys.size(); ++i) { |
|
|
|
if (HasKey(S128KeyType::Package2, i)) |
|
|
|
if (HasKey(S128KeyType::Package2, i)) { |
|
|
|
package2_keys[i] = GetKey(S128KeyType::Package2, i); |
|
|
|
} |
|
|
|
} |
|
|
|
data.DecryptPackage2(package2_keys, Package2Type::NormalMain); |
|
|
|
|
|
|
|
@ -1150,12 +1210,15 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const { |
|
|
|
|
|
|
|
bool KeyManager::AddTicketCommon(Ticket raw) { |
|
|
|
const auto rsa_key = GetETicketRSAKey(); |
|
|
|
if (rsa_key == RSAKeyPair<2048>{}) |
|
|
|
if (rsa_key == RSAKeyPair<2048>{}) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
const auto pair = ParseTicket(raw, rsa_key); |
|
|
|
if (!pair) |
|
|
|
if (!pair) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
const auto& [rid, key] = *pair; |
|
|
|
u128 rights_id; |
|
|
|
std::memcpy(rights_id.data(), rid.data(), rid.size()); |
|
|
|
@ -1166,12 +1229,15 @@ bool KeyManager::AddTicketCommon(Ticket raw) { |
|
|
|
|
|
|
|
bool KeyManager::AddTicketPersonalized(Ticket raw) { |
|
|
|
const auto rsa_key = GetETicketRSAKey(); |
|
|
|
if (rsa_key == RSAKeyPair<2048>{}) |
|
|
|
if (rsa_key == RSAKeyPair<2048>{}) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
const auto pair = ParseTicket(raw, rsa_key); |
|
|
|
if (!pair) |
|
|
|
if (!pair) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
const auto& [rid, key] = *pair; |
|
|
|
u128 rights_id; |
|
|
|
std::memcpy(rights_id.data(), rid.data(), rid.size()); |
|
|
|
|