add basic support for title installing

This commit is contained in:
ITotalJustice
2025-04-20 14:12:12 +01:00
parent 651d9fa495
commit 89e82927ee
40 changed files with 3661 additions and 316 deletions

View File

@@ -0,0 +1,67 @@
#include "yati/container/nsp.hpp"
#include "defines.hpp"
#include "log.hpp"
#include <memory>
namespace sphaira::yati::container {
namespace {
#define PFS0_MAGIC 0x30534650
struct Pfs0Header {
u32 magic;
u32 total_files;
u32 string_table_size;
u32 padding;
};
struct Pfs0FileTableEntry {
u64 data_offset;
u64 data_size;
u32 name_offset;
u32 padding;
};
} // namespace
Result Nsp::Validate(source::Base* source) {
u32 magic;
u64 bytes_read;
R_TRY(source->Read(std::addressof(magic), 0, sizeof(magic), std::addressof(bytes_read)));
R_UNLESS(magic == PFS0_MAGIC, 0x1);
R_SUCCEED();
}
Result Nsp::GetCollections(Collections& out) {
u64 bytes_read;
s64 off = 0;
// get header
Pfs0Header header{};
R_TRY(m_source->Read(std::addressof(header), off, sizeof(header), std::addressof(bytes_read)));
R_UNLESS(header.magic == PFS0_MAGIC, 0x1);
off += bytes_read;
// get file table
std::vector<Pfs0FileTableEntry> file_table(header.total_files);
R_TRY(m_source->Read(file_table.data(), off, file_table.size() * sizeof(Pfs0FileTableEntry), std::addressof(bytes_read)))
off += bytes_read;
// get string table
std::vector<char> string_table(header.string_table_size);
R_TRY(m_source->Read(string_table.data(), off, string_table.size(), std::addressof(bytes_read)))
off += bytes_read;
out.reserve(header.total_files);
for (u32 i = 0; i < header.total_files; i++) {
CollectionEntry entry;
entry.name = string_table.data() + file_table[i].name_offset;
entry.offset = off + file_table[i].data_offset;
entry.size = file_table[i].data_size;
out.emplace_back(entry);
}
R_SUCCEED();
}
} // namespace sphaira::yati::container

View File

@@ -0,0 +1,95 @@
#include "yati/container/xci.hpp"
#include "defines.hpp"
#include "log.hpp"
namespace sphaira::yati::container {
namespace {
#define XCI_MAGIC std::byteswap(0x48454144)
#define HFS0_MAGIC 0x30534648
#define HFS0_HEADER_OFFSET 0xF000
struct Hfs0Header {
u32 magic;
u32 total_files;
u32 string_table_size;
u32 padding;
};
struct Hfs0FileTableEntry {
u64 data_offset;
u64 data_size;
u32 name_offset;
u32 hash_size;
u64 padding;
u8 hash[0x20];
};
struct Hfs0 {
Hfs0Header header{};
std::vector<Hfs0FileTableEntry> file_table{};
std::vector<std::string> string_table{};
s64 data_offset{};
};
Result Hfs0GetPartition(source::Base* source, s64 off, Hfs0& out) {
u64 bytes_read;
// get header
R_TRY(source->Read(std::addressof(out.header), off, sizeof(out.header), std::addressof(bytes_read)));
R_UNLESS(out.header.magic == HFS0_MAGIC, 0x1);
off += bytes_read;
// get file table
out.file_table.resize(out.header.total_files);
R_TRY(source->Read(out.file_table.data(), off, out.file_table.size() * sizeof(Hfs0FileTableEntry), std::addressof(bytes_read)))
off += bytes_read;
// get string table
std::vector<char> string_table(out.header.string_table_size);
R_TRY(source->Read(string_table.data(), off, string_table.size(), std::addressof(bytes_read)))
off += bytes_read;
for (u32 i = 0; i < out.header.total_files; i++) {
out.string_table.emplace_back(string_table.data() + out.file_table[i].name_offset);
}
out.data_offset = off;
R_SUCCEED();
}
} // namespace
Result Xci::Validate(source::Base* source) {
u32 magic;
u64 bytes_read;
R_TRY(source->Read(std::addressof(magic), 0x100, sizeof(magic), std::addressof(bytes_read)));
R_UNLESS(magic == XCI_MAGIC, 0x1);
R_SUCCEED();
}
Result Xci::GetCollections(Collections& out) {
Hfs0 root{};
R_TRY(Hfs0GetPartition(m_source, HFS0_HEADER_OFFSET, root));
for (u32 i = 0; i < root.header.total_files; i++) {
if (root.string_table[i] == "secure") {
Hfs0 secure{};
R_TRY(Hfs0GetPartition(m_source, root.data_offset + root.file_table[i].data_offset, secure));
for (u32 i = 0; i < secure.header.total_files; i++) {
CollectionEntry entry;
entry.name = secure.string_table[i];
entry.offset = secure.data_offset + secure.file_table[i].data_offset;
entry.size = secure.file_table[i].data_size;
out.emplace_back(entry);
}
R_SUCCEED();
}
}
return 0x1;
}
} // namespace sphaira::yati::container

View File

@@ -0,0 +1,122 @@
#include "yati/nx/es.hpp"
#include "yati/nx/crypto.hpp"
#include "yati/nx/nxdumptool_rsa.h"
#include "defines.hpp"
#include "log.hpp"
#include <memory>
#include <cstring>
namespace sphaira::es {
namespace {
} // namespace
Result ImportTicket(Service* srv, const void* tik_buf, u64 tik_size, const void* cert_buf, u64 cert_size) {
return serviceDispatch(srv, 1,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In, SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
.buffers = { { tik_buf, tik_size }, { cert_buf, cert_size } });
}
typedef enum {
TikPropertyMask_None = 0,
TikPropertyMask_PreInstallation = BIT(0), ///< Determines if the title comes pre-installed on the device. Most likely unused -- a remnant from previous ticket formats.
TikPropertyMask_SharedTitle = BIT(1), ///< Determines if the title holds shared contents only. Most likely unused -- a remnant from previous ticket formats.
TikPropertyMask_AllContents = BIT(2), ///< Determines if the content index mask shall be bypassed. Most likely unused -- a remnant from previous ticket formats.
TikPropertyMask_DeviceLinkIndepedent = BIT(3), ///< Determines if the console should *not* connect to the Internet to verify if the title's being used by the primary console.
TikPropertyMask_Volatile = BIT(4), ///< Determines if the ticket copy inside ticket.bin is available after reboot. Can be encrypted.
TikPropertyMask_ELicenseRequired = BIT(5), ///< Determines if the console should connect to the Internet to perform license verification.
TikPropertyMask_Count = 6 ///< Total values supported by this enum.
} TikPropertyMask;
Result GetTicketDataOffset(std::span<const u8> ticket, u64& out) {
log_write("inside es\n");
u32 signature_type;
std::memcpy(std::addressof(signature_type), ticket.data(), sizeof(signature_type));
u32 signature_size;
switch (signature_type) {
case es::TicketSigantureType_RSA_4096_SHA1: log_write("RSA-4096 PKCS#1 v1.5 with SHA-1\n"); signature_size = 0x200; break;
case es::TicketSigantureType_RSA_2048_SHA1: log_write("RSA-2048 PKCS#1 v1.5 with SHA-1\n"); signature_size = 0x100; break;
case es::TicketSigantureType_ECDSA_SHA1: log_write("ECDSA with SHA-1\n"); signature_size = 0x3C; break;
case es::TicketSigantureType_RSA_4096_SHA256: log_write("RSA-4096 PKCS#1 v1.5 with SHA-256\n"); signature_size = 0x200; break;
case es::TicketSigantureType_RSA_2048_SHA256: log_write("RSA-2048 PKCS#1 v1.5 with SHA-256\n"); signature_size = 0x100; break;
case es::TicketSigantureType_ECDSA_SHA256: log_write("ECDSA with SHA-256\n"); signature_size = 0x3C; break;
case es::TicketSigantureType_HMAC_SHA1_160: log_write("HMAC-SHA1-160\n"); signature_size = 0x14; break;
default: log_write("unknown ticket\n"); return 0x1;
}
// align-up to 0x40.
out = ((signature_size + sizeof(signature_type)) + 0x3F) & ~0x3F;
R_SUCCEED();
}
Result GetTicketData(std::span<const u8> ticket, es::TicketData* out) {
u64 data_off;
R_TRY(GetTicketDataOffset(ticket, data_off));
std::memcpy(out, ticket.data() + data_off, sizeof(*out));
// validate ticket data.
R_UNLESS(out->ticket_version1 == 0x2, Result_InvalidTicketVersion); // must be version 2.
R_UNLESS(out->title_key_type == es::TicketTitleKeyType_Common || out->title_key_type == es::TicketTitleKeyType_Personalized, Result_InvalidTicketKeyType);
R_UNLESS(out->master_key_revision <= 0x20, Result_InvalidTicketKeyRevision);
R_SUCCEED();
}
Result SetTicketData(std::span<u8> ticket, const es::TicketData* in) {
u64 data_off;
R_TRY(GetTicketDataOffset(ticket, data_off));
std::memcpy(ticket.data() + data_off, in, sizeof(*in));
R_SUCCEED();
}
Result GetTitleKey(keys::KeyEntry& out, const TicketData& data, const keys::Keys& keys) {
if (data.title_key_type == es::TicketTitleKeyType_Common) {
std::memcpy(std::addressof(out), data.title_key_block, sizeof(out));
} else if (data.title_key_type == es::TicketTitleKeyType_Personalized) {
auto rsa_key = (const es::EticketRsaDeviceKey*)keys.eticket_device_key.key;
log_write("personalised ticket\n");
log_write("master_key_revision: %u\n", data.master_key_revision);
log_write("license_type: %u\n", data.license_type);
log_write("properties_bitfield: 0x%X\n", data.properties_bitfield);
log_write("device_id: 0x%lX vs 0x%lX\n", data.device_id, std::byteswap(rsa_key->device_id));
R_UNLESS(data.device_id == std::byteswap(rsa_key->device_id), 0x1);
log_write("device id is same\n");
u8 out_keydata[RSA2048_BYTES]{};
size_t out_keydata_size;
R_UNLESS(rsa2048OaepDecrypt(out_keydata, sizeof(out_keydata), data.title_key_block, rsa_key->modulus, &rsa_key->public_exponent, sizeof(rsa_key->public_exponent), rsa_key->private_exponent, sizeof(rsa_key->private_exponent), NULL, 0, &out_keydata_size), 0x1);
R_UNLESS(out_keydata_size >= sizeof(out), 0x1);
std::memcpy(std::addressof(out), out_keydata, sizeof(out));
} else {
R_THROW(0x1);
}
R_SUCCEED();
}
Result DecryptTitleKey(keys::KeyEntry& out, u8 key_gen, const keys::Keys& keys) {
keys::KeyEntry title_kek;
R_TRY(keys.GetTitleKek(std::addressof(title_kek), key_gen));
crypto::cryptoAes128(std::addressof(out), std::addressof(out), std::addressof(title_kek), false);
R_SUCCEED();
}
// todo: i thought i already wrote the code for this??
// todo: patch the ticket.
Result PatchTicket(std::span<u8> ticket, const keys::Keys& keys, bool convert_personalised) {
TicketData data;
R_TRY(GetTicketData(ticket, &data));
if (data.title_key_type == es::TicketTitleKeyType_Common) {
// todo: verify common signature
} else if (data.title_key_type == es::TicketTitleKeyType_Personalized && convert_personalised) {
}
R_SUCCEED();
}
} // namespace sphaira::es

View File

@@ -0,0 +1,130 @@
#include "yati/nx/keys.hpp"
#include "yati/nx/nca.hpp"
#include "yati/nx/es.hpp"
#include "yati/nx/crypto.hpp"
#include "defines.hpp"
#include "log.hpp"
#include <minIni.h>
#include <memory>
#include <bit>
#include <cstring>
namespace sphaira::keys {
namespace {
constexpr u8 HEADER_KEK_SRC[0x10] = {
0x1F, 0x12, 0x91, 0x3A, 0x4A, 0xCB, 0xF0, 0x0D, 0x4C, 0xDE, 0x3A, 0xF6, 0xD5, 0x23, 0x88, 0x2A
};
constexpr u8 HEADER_KEY_SRC[0x20] = {
0x5A, 0x3E, 0xD8, 0x4F, 0xDE, 0xC0, 0xD8, 0x26, 0x31, 0xF7, 0xE2, 0x5D, 0x19, 0x7B, 0xF5, 0xD0,
0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2
};
} // namespace
void parse_hex_key(void* key, const char* hex) {
char low[0x11]{};
char upp[0x11]{};
std::memcpy(low, hex, 0x10);
std::memcpy(upp, hex + 0x10, 0x10);
*(u64*)key = std::byteswap(std::strtoul(low, nullptr, 0x10));
*(u64*)((u8*)key + 8) = std::byteswap(std::strtoul(upp, nullptr, 0x10));
}
Result parse_keys(Keys& out, bool read_from_file) {
static constexpr auto find_key = [](const char* key, const char* value, const char* search_key, KeySection& key_section) -> bool {
if (!std::strncmp(key, search_key, std::strlen(search_key))) {
// get key index.
char* end;
const auto key_value_str = key + std::strlen(search_key);
const auto index = std::strtoul(key_value_str, &end, 0x10);
if (end && end != key_value_str && index < 0x20) {
KeyEntry keak;
parse_hex_key(std::addressof(keak), value);
key_section[index] = keak;
return true;
}
}
return false;
};
static constexpr auto find_key_single = [](const char* key, const char* value, const char* search_key, KeyEntry& key_entry) -> bool {
if (!std::strcmp(key, search_key)) {
parse_hex_key(std::addressof(key_entry), value);
return true;
}
return false;
};
static constexpr auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
auto keys = static_cast<Keys*>(UserData);
auto key_text_key_area_key_app = "key_area_key_application_";
auto key_text_key_area_key_oce = "key_area_key_ocean_";
auto key_text_key_area_key_sys = "key_area_key_system_";
auto key_text_titlekek = "titlekek_";
auto key_text_master_key = "master_key_";
auto key_text_eticket_rsa_kek = keys->eticket_device_key.generation ? "eticket_rsa_kek_personalized" : "eticket_rsa_kek";
if (find_key(Key, Value, key_text_key_area_key_app, keys->key_area_key[nca::KeyAreaEncryptionKeyIndex_Application])) {
return 1;
} else if (find_key(Key, Value, key_text_key_area_key_oce, keys->key_area_key[nca::KeyAreaEncryptionKeyIndex_Ocean])) {
return 1;
} else if (find_key(Key, Value, key_text_key_area_key_sys, keys->key_area_key[nca::KeyAreaEncryptionKeyIndex_System])) {
return 1;
} else if (find_key(Key, Value, key_text_titlekek, keys->titlekek)) {
return 1;
} else if (find_key(Key, Value, key_text_master_key, keys->master_key)) {
return 1;
} else if (find_key_single(Key, Value, key_text_eticket_rsa_kek, keys->eticket_rsa_kek)) {
log_write("found key single: key: %s value %s\n", Key, Value);
return 1;
}
return 1;
};
R_TRY(splCryptoInitialize());
ON_SCOPE_EXIT(splCryptoExit());
u8 header_kek[0x20];
R_TRY(splCryptoGenerateAesKek(HEADER_KEK_SRC, 0, 0, header_kek));
R_TRY(splCryptoGenerateAesKey(header_kek, HEADER_KEY_SRC, out.header_key));
R_TRY(splCryptoGenerateAesKey(header_kek, HEADER_KEY_SRC + 0x10, out.header_key + 0x10));
if (read_from_file) {
// get eticket device key, needed for decrypting personalised tickets.
R_TRY(setcalInitialize());
ON_SCOPE_EXIT(setcalExit());
R_TRY(setcalGetEticketDeviceKey(std::addressof(out.eticket_device_key)));
R_UNLESS(ini_browse(cb, std::addressof(out), "/switch/prod.keys"), 0x1);
// decrypt eticket device key.
if (out.eticket_rsa_kek.IsValid()) {
auto rsa_key = (es::EticketRsaDeviceKey*)out.eticket_device_key.key;
Aes128CtrContext eticket_aes_ctx{};
aes128CtrContextCreate(&eticket_aes_ctx, &out.eticket_rsa_kek, rsa_key->ctr);
aes128CtrCrypt(&eticket_aes_ctx, &(rsa_key->private_exponent), &(rsa_key->private_exponent), sizeof(es::EticketRsaDeviceKey) - sizeof(rsa_key->ctr));
const auto public_exponent = std::byteswap(rsa_key->public_exponent);
if (public_exponent != 0x10001) {
log_write("etick decryption fail: 0x%X\n", public_exponent);
if (public_exponent == 0) {
log_write("eticket device id is NULL\n");
}
R_THROW(0x1);
} else {
log_write("eticket match\n");
}
}
}
R_SUCCEED();
}
} // namespace sphaira::keys

View File

@@ -0,0 +1,154 @@
#include "yati/nx/nca.hpp"
#include "yati/nx/crypto.hpp"
#include "yati/nx/nxdumptool_rsa.h"
#include "log.hpp"
namespace sphaira::nca {
namespace {
constexpr u8 g_key_area_key_application_source[0x10] = { 0x7F, 0x59, 0x97, 0x1E, 0x62, 0x9F, 0x36, 0xA1, 0x30, 0x98, 0x06, 0x6F, 0x21, 0x44, 0xC3, 0x0D };
constexpr u8 g_key_area_key_ocean_source[0x10] = { 0x32, 0x7D, 0x36, 0x08, 0x5A, 0xD1, 0x75, 0x8D, 0xAB, 0x4E, 0x6F, 0xBA, 0xA5, 0x55, 0xD8, 0x82 };
constexpr u8 g_key_area_key_system_source[0x10] = { 0x87, 0x45, 0xF1, 0xBB, 0xA6, 0xBE, 0x79, 0x64, 0x7D, 0x04, 0x8B, 0xA6, 0x7B, 0x5F, 0xDA, 0x4A };
constexpr const u8* g_key_area_key[] = {
g_key_area_key_application_source,
g_key_area_key_ocean_source,
g_key_area_key_system_source
};
const unsigned char nca_hdr_fixed_key_moduli_retail[0x2][0x100] = { /* Fixed RSA key used to validate NCA signature 0. */
{
0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F,
0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58,
0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16,
0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2,
0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF,
0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67,
0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26,
0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C,
0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE,
0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86,
0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35,
0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B,
0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29,
0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40,
0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81,
0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03,
},
{
0xAD, 0xE3, 0xE1, 0xFA, 0x04, 0x35, 0xE5, 0xB6, 0xDD, 0x49, 0xEA, 0x89, 0x29, 0xB1, 0xFF, 0xB6,
0x43, 0xDF, 0xCA, 0x96, 0xA0, 0x4A, 0x13, 0xDF, 0x43, 0xD9, 0x94, 0x97, 0x96, 0x43, 0x65, 0x48,
0x70, 0x58, 0x33, 0xA2, 0x7D, 0x35, 0x7B, 0x96, 0x74, 0x5E, 0x0B, 0x5C, 0x32, 0x18, 0x14, 0x24,
0xC2, 0x58, 0xB3, 0x6C, 0x22, 0x7A, 0xA1, 0xB7, 0xCB, 0x90, 0xA7, 0xA3, 0xF9, 0x7D, 0x45, 0x16,
0xA5, 0xC8, 0xED, 0x8F, 0xAD, 0x39, 0x5E, 0x9E, 0x4B, 0x51, 0x68, 0x7D, 0xF8, 0x0C, 0x35, 0xC6,
0x3F, 0x91, 0xAE, 0x44, 0xA5, 0x92, 0x30, 0x0D, 0x46, 0xF8, 0x40, 0xFF, 0xD0, 0xFF, 0x06, 0xD2,
0x1C, 0x7F, 0x96, 0x18, 0xDC, 0xB7, 0x1D, 0x66, 0x3E, 0xD1, 0x73, 0xBC, 0x15, 0x8A, 0x2F, 0x94,
0xF3, 0x00, 0xC1, 0x83, 0xF1, 0xCD, 0xD7, 0x81, 0x88, 0xAB, 0xDF, 0x8C, 0xEF, 0x97, 0xDD, 0x1B,
0x17, 0x5F, 0x58, 0xF6, 0x9A, 0xE9, 0xE8, 0xC2, 0x2F, 0x38, 0x15, 0xF5, 0x21, 0x07, 0xF8, 0x37,
0x90, 0x5D, 0x2E, 0x02, 0x40, 0x24, 0x15, 0x0D, 0x25, 0xB7, 0x26, 0x5D, 0x09, 0xCC, 0x4C, 0xF4,
0xF2, 0x1B, 0x94, 0x70, 0x5A, 0x9E, 0xEE, 0xED, 0x77, 0x77, 0xD4, 0x51, 0x99, 0xF5, 0xDC, 0x76,
0x1E, 0xE3, 0x6C, 0x8C, 0xD1, 0x12, 0xD4, 0x57, 0xD1, 0xB6, 0x83, 0xE4, 0xE4, 0xFE, 0xDA, 0xE9,
0xB4, 0x3B, 0x33, 0xE5, 0x37, 0x8A, 0xDF, 0xB5, 0x7F, 0x89, 0xF1, 0x9B, 0x9E, 0xB0, 0x15, 0xB2,
0x3A, 0xFE, 0xEA, 0x61, 0x84, 0x5B, 0x7D, 0x4B, 0x23, 0x12, 0x0B, 0x83, 0x12, 0xF2, 0x22, 0x6B,
0xB9, 0x22, 0x96, 0x4B, 0x26, 0x0B, 0x63, 0x5E, 0x96, 0x57, 0x52, 0xA3, 0x67, 0x64, 0x22, 0xCA,
0xD0, 0x56, 0x3E, 0x74, 0xB5, 0x98, 0x1F, 0x0D, 0xF8, 0xB3, 0x34, 0xE6, 0x98, 0x68, 0x5A, 0xAD,
}
};
const unsigned char acid_fixed_key_moduli_retail[0x2][0x100] = { /* Fixed RSA keys used to validate ACID signatures. */
{
0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D,
0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50,
0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57,
0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20,
0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21,
0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2,
0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4,
0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE,
0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10,
0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53,
0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD,
0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08,
0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A,
0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA,
0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B,
0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD,
},
{
0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90,
0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5,
0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF,
0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E,
0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A,
0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D,
0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01,
0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8,
0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43,
0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6,
0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4,
0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C,
0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D,
0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83,
0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03,
0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03
}
};
} // namespace
Result DecryptKeak(const keys::Keys& keys, Header& header) {
const auto key_generation = header.GetKeyGeneration();
// try with spl.
keys::KeyEntry keak;
if (R_SUCCEEDED(splCryptoGenerateAesKek(g_key_area_key[header.kaek_index], key_generation, 0, &keak))) {
for (auto& key_area : header.key_area) {
R_TRY(splCryptoGenerateAesKey(&keak, std::addressof(key_area), std::addressof(key_area)));
}
} else {
// failed with spl, try using keys.
R_TRY(keys.GetNcaKeyArea(&keak, key_generation, header.kaek_index));
for (auto& key_area : header.key_area) {
crypto::cryptoAes128(std::addressof(key_area), std::addressof(key_area), std::addressof(keak), false);
}
}
R_SUCCEED();
}
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation) {
header.SetKeyGeneration(key_generation);
keys::KeyEntry keak;
R_TRY(keys.GetNcaKeyArea(&keak, key_generation, header.kaek_index));
log_write("re-encrypting with: 0x%X\n", key_generation);
for (auto& key_area : header.key_area) {
crypto::cryptoAes128(std::addressof(key_area), std::addressof(key_area), std::addressof(keak), true);
}
R_SUCCEED();
}
Result VerifyFixedKey(const Header& header) {
R_UNLESS(header.sig_key_gen < std::size(nca_hdr_fixed_key_moduli_retail), 0x1);
auto mod = nca_hdr_fixed_key_moduli_retail[header.sig_key_gen];
const u8 E[3] = { 1, 0, 1 };
if (!rsa2048VerifySha256BasedPssSignature(&header.magic, 0x200, header.rsa_fixed_key, mod, E, sizeof(E))) {
auto new_header = header;
// if failed, detect if this is a eshop/xci convert.
new_header.distribution_type ^= 1;
if (!rsa2048VerifySha256BasedPssSignature(&new_header.magic, 0x200, new_header.rsa_fixed_key, mod, E, sizeof(E))) {
log_write("FAILED nca header hash\n");
R_THROW(0x1);
} else {
log_write("WARNING! nca is converted! distribution_type: %u\n", new_header.distribution_type);
R_SUCCEED();
}
}
R_SUCCEED();
}
} // namespace sphaira::nca

View File

@@ -0,0 +1,34 @@
#include "yati/nx/ncm.hpp"
#include "defines.hpp"
#include <memory>
namespace sphaira::ncm {
namespace {
} // namespace
auto GetAppId(const NcmContentMetaKey& key) -> u64 {
if (key.type == NcmContentMetaType_Patch) {
return key.id ^ 0x800;
} else if (key.type == NcmContentMetaType_AddOnContent) {
return (key.id ^ 0x1000) & ~0xFFF;
} else {
return key.id;
}
}
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id) {
bool has;
R_TRY(ncmContentStorageHas(cs, std::addressof(has), content_id));
if (has) {
R_TRY(ncmContentStorageDelete(cs, content_id));
}
R_SUCCEED();
}
Result Register(NcmContentStorage* cs, const NcmContentId *content_id, const NcmPlaceHolderId *placeholder_id) {
R_TRY(Delete(cs, content_id));
return ncmContentStorageRegister(cs, content_id, placeholder_id);
}
} // namespace sphaira::ncm

View File

@@ -0,0 +1,39 @@
#include "yati/nx/ns.hpp"
namespace sphaira::ns {
namespace {
} // namespace
Result PushApplicationRecord(Service* srv, u64 tid, const ncm::ContentStorageRecord* records, u32 count) {
const struct {
u8 last_modified_event;
u8 padding[0x7];
u64 tid;
} in = { ApplicationRecordType_Installed, {0}, tid };
return serviceDispatchIn(srv, 16, in,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
.buffers = { { records, sizeof(*records) * count } });
}
Result ListApplicationRecordContentMeta(Service* srv, u64 offset, u64 tid, ncm::ContentStorageRecord* out_records, u32 count, s32* entries_read) {
struct {
u64 offset;
u64 tid;
} in = { offset, tid };
return serviceDispatchInOut(srv, 17, in, *entries_read,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { out_records, sizeof(*out_records) * count } });
}
Result DeleteApplicationRecord(Service* srv, u64 tid) {
return serviceDispatchIn(srv, 27, tid);
}
Result InvalidateApplicationControlCache(Service* srv, u64 tid) {
return serviceDispatchIn(srv, 404, tid);
}
} // namespace sphaira::ns

View File

@@ -0,0 +1,158 @@
/*
* rsa.c
*
* Copyright (c) 2018-2019, SciresM.
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nxdumptool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "yati/nx/nxdumptool_rsa.h"
#include "log.hpp"
#include <switch.h>
#include <string.h>
#include <mbedtls/rsa.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/pk.h>
#define LOG_MSG_ERROR(...) log_write(__VA_ARGS__)
/* Function prototypes. */
static bool rsa2048VerifySha256BasedSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, \
bool use_pss);
bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size)
{
return rsa2048VerifySha256BasedSignature(data, data_size, signature, modulus, public_exponent, public_exponent_size, true);
}
bool rsa2048VerifySha256BasedPkcs1v15Signature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size)
{
return rsa2048VerifySha256BasedSignature(data, data_size, signature, modulus, public_exponent, public_exponent_size, false);
}
bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, const void *private_exponent, \
size_t private_exponent_size, const void *label, size_t label_size, size_t *out_size)
{
if (!dst || !dst_size || !signature || !modulus || !public_exponent || !public_exponent_size || !private_exponent || !private_exponent_size || (!label && label_size) || (label && !label_size) || \
!out_size)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
mbedtls_entropy_context entropy = {0};
mbedtls_ctr_drbg_context ctr_drbg = {0};
mbedtls_rsa_context rsa = {0};
const char *pers = __func__;
int mbedtls_ret = 0;
bool ret = false;
/* Initialize contexts. */
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
/* Seed the random number generator. */
mbedtls_ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const u8*)pers, strlen(pers));
if (mbedtls_ret != 0)
{
LOG_MSG_ERROR("mbedtls_ctr_drbg_seed failed! (%d).", mbedtls_ret);
goto end;
}
/* Import RSA parameters. */
mbedtls_ret = mbedtls_rsa_import_raw(&rsa, (const u8*)modulus, RSA2048_BYTES, NULL, 0, NULL, 0, (const u8*)private_exponent, private_exponent_size, (const u8*)public_exponent, public_exponent_size);
if (mbedtls_ret != 0)
{
LOG_MSG_ERROR("mbedtls_rsa_import_raw failed! (%d).", mbedtls_ret);
goto end;
}
/* Derive RSA prime factors. */
mbedtls_ret = mbedtls_rsa_complete(&rsa);
if (mbedtls_ret != 0)
{
LOG_MSG_ERROR("mbedtls_rsa_complete failed! (%d).", mbedtls_ret);
goto end;
}
/* Perform RSA-OAEP decryption. */
mbedtls_ret = mbedtls_rsa_rsaes_oaep_decrypt(&rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, (const u8*)label, label_size, out_size, (const u8*)signature, (u8*)dst, dst_size);
if (mbedtls_ret != 0)
{
LOG_MSG_ERROR("mbedtls_rsa_rsaes_oaep_decrypt failed! (%d).", mbedtls_ret);
goto end;
}
ret = true;
end:
mbedtls_rsa_free(&rsa);
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
return ret;
}
static bool rsa2048VerifySha256BasedSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, \
bool use_pss)
{
if (!data || !data_size || !signature || !modulus || !public_exponent || !public_exponent_size)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
int mbedtls_ret = 0;
mbedtls_rsa_context rsa = {0};
u8 hash[SHA256_HASH_SIZE] = {0};
bool ret = false;
/* Initialize RSA context. */
mbedtls_rsa_init(&rsa, use_pss ? MBEDTLS_RSA_PKCS_V21 : MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_SHA256);
/* Import RSA parameters. */
mbedtls_ret = mbedtls_rsa_import_raw(&rsa, (const u8*)modulus, RSA2048_BYTES, NULL, 0, NULL, 0, NULL, 0, (const u8*)public_exponent, public_exponent_size);
if (mbedtls_ret != 0)
{
LOG_MSG_ERROR("mbedtls_rsa_import_raw failed! (%d).", mbedtls_ret);
goto end;
}
/* Calculate SHA-256 checksum for the input data. */
sha256CalculateHash(hash, data, data_size);
/* Verify signature. */
mbedtls_ret = (use_pss ? mbedtls_rsa_rsassa_pss_verify(&rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, SHA256_HASH_SIZE, hash, (const u8*)signature) : \
mbedtls_rsa_rsassa_pkcs1_v15_verify(&rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, SHA256_HASH_SIZE, hash, (const u8*)signature));
if (mbedtls_ret != 0)
{
LOG_MSG_ERROR("mbedtls_rsa_rsassa_%s_verify failed! (%d).", use_pss ? "pss" : "pkcs1_v15", mbedtls_ret);
goto end;
}
ret = true;
end:
mbedtls_rsa_free(&rsa);
return ret;
}

View File

@@ -0,0 +1,20 @@
#include "yati/source/file.hpp"
namespace sphaira::yati::source {
File::File(FsFileSystem* fs, const fs::FsPath& path) {
m_open_result = fsFsOpenFile(fs, path, FsOpenMode_Read, std::addressof(m_file));
}
File::~File() {
if (R_SUCCEEDED(GetOpenResult())) {
fsFileClose(std::addressof(m_file));
}
}
Result File::Read(void* buf, s64 off, s64 size, u64* bytes_read) {
R_TRY(GetOpenResult());
return fsFileRead(std::addressof(m_file), off, buf, size, 0, bytes_read);
}
} // namespace sphaira::yati::source

View File

@@ -0,0 +1,28 @@
#include "yati/source/stdio.hpp"
namespace sphaira::yati::source {
Stdio::Stdio(const char* path) {
m_file = std::fopen(path, "rb");
if (!m_file) {
m_open_result = fsdevGetLastResult();
}
}
Stdio::~Stdio() {
if (R_SUCCEEDED(GetOpenResult())) {
std::fclose(m_file);
}
}
Result Stdio::Read(void* buf, s64 off, s64 size, u64* bytes_read) {
R_TRY(GetOpenResult());
std::fseek(m_file, off, SEEK_SET);
R_TRY(fsdevGetLastResult());
*bytes_read = std::fread(buf, 1, size, m_file);
return fsdevGetLastResult();
}
} // namespace sphaira::yati::source

1317
sphaira/source/yati/yati.cpp Normal file

File diff suppressed because it is too large Load Diff