add basic support for title installing
This commit is contained in:
67
sphaira/source/yati/container/nsp.cpp
Normal file
67
sphaira/source/yati/container/nsp.cpp
Normal 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
|
||||
95
sphaira/source/yati/container/xci.cpp
Normal file
95
sphaira/source/yati/container/xci.cpp
Normal 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
|
||||
122
sphaira/source/yati/nx/es.cpp
Normal file
122
sphaira/source/yati/nx/es.cpp
Normal 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
|
||||
130
sphaira/source/yati/nx/keys.cpp
Normal file
130
sphaira/source/yati/nx/keys.cpp
Normal 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
|
||||
154
sphaira/source/yati/nx/nca.cpp
Normal file
154
sphaira/source/yati/nx/nca.cpp
Normal 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
|
||||
34
sphaira/source/yati/nx/ncm.cpp
Normal file
34
sphaira/source/yati/nx/ncm.cpp
Normal 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
|
||||
39
sphaira/source/yati/nx/ns.cpp
Normal file
39
sphaira/source/yati/nx/ns.cpp
Normal 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
|
||||
158
sphaira/source/yati/nx/nxdumptool_rsa.c
Normal file
158
sphaira/source/yati/nx/nxdumptool_rsa.c
Normal 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;
|
||||
}
|
||||
20
sphaira/source/yati/source/file.cpp
Normal file
20
sphaira/source/yati/source/file.cpp
Normal 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
|
||||
28
sphaira/source/yati/source/stdio.cpp
Normal file
28
sphaira/source/yati/source/stdio.cpp
Normal 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
1317
sphaira/source/yati/yati.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user