|
|
|
@ -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());
|
|
|
|
|