Add CTCert and DeviceID support

This commit is contained in:
PabloMK7 2023-11-16 17:59:45 +01:00
parent e39501556c
commit dedd48c7da
7 changed files with 222 additions and 11 deletions

View file

@ -241,20 +241,28 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
connect(ui->button_secure_info, &QPushButton::clicked, this, [this] { connect(ui->button_secure_info, &QPushButton::clicked, this, [this] {
ui->button_secure_info->setEnabled(false); ui->button_secure_info->setEnabled(false);
const QString file_path_qtstr = const QString file_path_qtstr = QFileDialog::getOpenFileName(
QFileDialog::getOpenFileName(this, tr("Select SecureInfo_A"), QString(), this, tr("Select SecureInfo_A/B"), QString(),
tr("SecureInfo_A (SecureInfo_A);;All Files (*.*)")); tr("SecureInfo_A/B (SecureInfo_A SecureInfo_B);;All Files (*.*)"));
ui->button_secure_info->setEnabled(true); ui->button_secure_info->setEnabled(true);
InstallSecureData(file_path_qtstr.toStdString(), cfg->GetSecureInfoAPath()); InstallSecureData(file_path_qtstr.toStdString(), cfg->GetSecureInfoAPath());
}); });
connect(ui->button_friend_code_seed, &QPushButton::clicked, this, [this] { connect(ui->button_friend_code_seed, &QPushButton::clicked, this, [this] {
ui->button_friend_code_seed->setEnabled(false); ui->button_friend_code_seed->setEnabled(false);
const QString file_path_qtstr = QFileDialog::getOpenFileName( const QString file_path_qtstr =
this, tr("Select LocalFriendCodeSeed_B"), QString(), QFileDialog::getOpenFileName(this, tr("Select LocalFriendCodeSeed_A/B"), QString(),
tr("LocalFriendCodeSeed_B (LocalFriendCodeSeed_B);;All Files (*.*)")); tr("LocalFriendCodeSeed_A/B (LocalFriendCodeSeed_A "
"LocalFriendCodeSeed_B);;All Files (*.*)"));
ui->button_friend_code_seed->setEnabled(true); ui->button_friend_code_seed->setEnabled(true);
InstallSecureData(file_path_qtstr.toStdString(), cfg->GetLocalFriendCodeSeedBPath()); InstallSecureData(file_path_qtstr.toStdString(), cfg->GetLocalFriendCodeSeedBPath());
}); });
connect(ui->button_ct_cert, &QPushButton::clicked, this, [this] {
ui->button_ct_cert->setEnabled(false);
const QString file_path_qtstr = QFileDialog::getOpenFileName(
this, tr("Select CTCert"), QString(), tr("CTCert.bin (*.bin);;All Files (*.*)"));
ui->button_ct_cert->setEnabled(true);
InstallCTCert(file_path_qtstr.toStdString());
});
for (u8 i = 0; i < country_names.size(); i++) { for (u8 i = 0; i < country_names.size(); i++) {
if (std::strcmp(country_names.at(i), "") != 0) { if (std::strcmp(country_names.at(i), "") != 0) {
@ -320,6 +328,7 @@ void ConfigureSystem::SetConfiguration() {
ui->edit_init_ticks_value->setText( ui->edit_init_ticks_value->setText(
QString::number(Settings::values.init_ticks_override.GetValue())); QString::number(Settings::values.init_ticks_override.GetValue()));
am = Service::AM::GetModule(system);
cfg = Service::CFG::GetModule(system); cfg = Service::CFG::GetModule(system);
ReadSystemSettings(); ReadSystemSettings();
@ -559,6 +568,19 @@ void ConfigureSystem::InstallSecureData(const std::string& from_path, const std:
RefreshSecureDataStatus(); RefreshSecureDataStatus();
} }
void ConfigureSystem::InstallCTCert(const std::string& from_path) {
std::string from =
FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault);
std::string to =
FileUtil::SanitizePath(am->GetCTCertPath(), FileUtil::DirectorySeparator::PlatformDefault);
if (from.empty() || from == to) {
return;
}
FileUtil::Copy(from, to);
am->InvalidateCTCertData();
RefreshSecureDataStatus();
}
void ConfigureSystem::RefreshSecureDataStatus() { void ConfigureSystem::RefreshSecureDataStatus() {
auto status_to_str = [](Service::CFG::SecureDataLoadStatus status) { auto status_to_str = [](Service::CFG::SecureDataLoadStatus status) {
switch (status) { switch (status) {
@ -579,6 +601,10 @@ void ConfigureSystem::RefreshSecureDataStatus() {
tr((std::string("Status: ") + status_to_str(cfg->LoadSecureInfoAFile())).c_str())); tr((std::string("Status: ") + status_to_str(cfg->LoadSecureInfoAFile())).c_str()));
ui->label_friend_code_seed_status->setText( ui->label_friend_code_seed_status->setText(
tr((std::string("Status: ") + status_to_str(cfg->LoadLocalFriendCodeSeedBFile())).c_str())); tr((std::string("Status: ") + status_to_str(cfg->LoadLocalFriendCodeSeedBFile())).c_str()));
ui->label_ct_cert_status->setText(
tr((std::string("Status: ") +
status_to_str(static_cast<Service::CFG::SecureDataLoadStatus>(am->LoadCTCertFile())))
.c_str()));
} }
void ConfigureSystem::RetranslateUI() { void ConfigureSystem::RetranslateUI() {

View file

@ -20,6 +20,12 @@ namespace Core {
class System; class System;
} }
namespace Service {
namespace AM {
class Module;
} // namespace AM
} // namespace Service
namespace Service { namespace Service {
namespace CFG { namespace CFG {
class Module; class Module;
@ -47,6 +53,7 @@ private:
void RefreshConsoleID(); void RefreshConsoleID();
void InstallSecureData(const std::string& from_path, const std::string& to_path); void InstallSecureData(const std::string& from_path, const std::string& to_path);
void InstallCTCert(const std::string& from_path);
void RefreshSecureDataStatus(); void RefreshSecureDataStatus();
void SetupPerGameUI(); void SetupPerGameUI();
@ -60,6 +67,7 @@ private:
ConfigurationShared::CheckState lle_applets; ConfigurationShared::CheckState lle_applets;
bool enabled = false; bool enabled = false;
std::shared_ptr<Service::AM::Module> am;
std::shared_ptr<Service::CFG::Module> cfg; std::shared_ptr<Service::CFG::Module> cfg;
std::u16string username; std::u16string username;
int birthmonth = 0; int birthmonth = 0;

View file

@ -80,6 +80,35 @@ struct TicketInfo {
static_assert(sizeof(TicketInfo) == 0x18, "Ticket info structure size is wrong"); static_assert(sizeof(TicketInfo) == 0x18, "Ticket info structure size is wrong");
bool CTCert::IsValid() const {
constexpr std::string_view expected_issuer_prod = "Nintendo CA - G3_NintendoCTR2prod";
constexpr std::string_view expected_issuer_dev = "Nintendo CA - G3_NintendoCTR2dev";
constexpr u32 expected_signature_type = 0x010005;
return signature_type == expected_signature_type &&
(std::string(issuer.data()) == expected_issuer_prod ||
std::string(issuer.data()) == expected_issuer_dev);
return false;
}
u32 CTCert::GetDeviceID() const {
constexpr std::string_view key_id_prefix = "CT";
const std::string key_id_str(key_id.data());
if (key_id_str.starts_with(key_id_prefix)) {
const std::string device_id =
key_id_str.substr(key_id_prefix.size(), key_id_str.find('-') - key_id_prefix.size());
char* end_ptr;
const u32 device_id_value = std::strtoul(device_id.c_str(), &end_ptr, 16);
if (*end_ptr == '\0') {
return device_id_value;
}
}
// Error
return 0;
}
class CIAFile::DecryptionState { class CIAFile::DecryptionState {
public: public:
std::vector<CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption> content; std::vector<CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption> content;
@ -1213,6 +1242,21 @@ void Module::Interface::GetTicketList(Kernel::HLERequestContext& ctx) {
ticket_list_count, ticket_index); ticket_list_count, ticket_index);
} }
void Module::Interface::GetDeviceID(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 deviceID = am->ct_cert.IsValid() ? am->ct_cert.GetDeviceID() : 0;
if (deviceID == 0) {
LOG_ERROR(Service_AM, "Invalid or missing CTCert");
}
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
rb.Push(RESULT_SUCCESS);
rb.Push(0);
rb.Push(deviceID);
}
void Module::Interface::NeedsCleanup(Kernel::HLERequestContext& ctx) { void Module::Interface::NeedsCleanup(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const auto media_type = rp.Pop<u8>(); const auto media_type = rp.Pop<u8>();
@ -1802,13 +1846,76 @@ void Module::serialize(Archive& ar, const unsigned int) {
} }
SERIALIZE_IMPL(Module) SERIALIZE_IMPL(Module)
Module::Module(Core::System& system) : system(system) { void Module::Interface::GetDeviceCert(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
[[maybe_unused]] u32 size = rp.Pop<u32>();
auto buffer = rp.PopMappedBuffer();
if (!am->ct_cert.IsValid()) {
LOG_ERROR(Service_AM, "Invalid or missing CTCert");
}
buffer.Write(&am->ct_cert, 0, std::min(sizeof(CTCert), buffer.GetSize()));
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
rb.Push(RESULT_SUCCESS);
rb.Push(0);
rb.PushMappedBuffer(buffer);
}
std::string Module::GetCTCertPath() {
return FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + "CTCert.bin";
}
void Module::InvalidateCTCertData() {
ct_cert = CTCert();
}
CTCertLoadStatus Module::LoadCTCertFile() {
if (ct_cert.IsValid()) {
return CTCertLoadStatus::Loaded;
}
std::string file_path = GetCTCertPath();
if (!FileUtil::Exists(file_path)) {
return CTCertLoadStatus::NotFound;
}
FileUtil::IOFile file(file_path, "rb");
if (!file.IsOpen()) {
return CTCertLoadStatus::IOError;
}
if (file.GetSize() != sizeof(CTCert)) {
return CTCertLoadStatus::Invalid;
}
if (file.ReadBytes(&ct_cert, sizeof(CTCert)) != sizeof(CTCert)) {
return CTCertLoadStatus::IOError;
}
if (!ct_cert.IsValid()) {
ct_cert = CTCert();
return CTCertLoadStatus::Invalid;
}
return CTCertLoadStatus::Loaded;
}
Module::Module() {
LoadCTCertFile();
}
Module::Module(Core::System& system) : kernel(&system.Kernel()) {
ScanForAllTitles(); ScanForAllTitles();
LoadCTCertFile();
system_updater_mutex = system.Kernel().CreateMutex(false, "AM::SystemUpdaterMutex"); system_updater_mutex = system.Kernel().CreateMutex(false, "AM::SystemUpdaterMutex");
} }
Module::Module(Kernel::KernelSystem& kernel) : kernel(&kernel) {}
Module::~Module() = default; Module::~Module() = default;
std::shared_ptr<Module> GetModule(Core::System& system) {
auto am = system.ServiceManager().GetService<Service::AM::Module::Interface>("am:u");
if (!am)
return nullptr;
return am->GetModule();
}
void InstallInterfaces(Core::System& system) { void InstallInterfaces(Core::System& system) {
auto& service_manager = system.ServiceManager(); auto& service_manager = system.ServiceManager();
auto am = std::make_shared<Module>(system); auto am = std::make_shared<Module>(system);

View file

@ -68,6 +68,31 @@ enum class InstallStatus : u32 {
ErrorEncrypted, ErrorEncrypted,
}; };
enum class CTCertLoadStatus {
Loaded,
NotFound,
Invalid,
IOError,
};
struct CTCert {
u32_be signature_type{};
std::array<u8, 0x1E> signature_r{};
std::array<u8, 0x1E> signature_s{};
INSERT_PADDING_BYTES(0x40){};
std::array<char, 0x40> issuer{};
u32_be key_type{};
std::array<char, 0x40> key_id{};
u32_be expiration_time{};
std::array<u8, 0x1E> public_key_x{};
std::array<u8, 0x1E> public_key_y{};
INSERT_PADDING_BYTES(0x3C){};
bool IsValid() const;
u32 GetDeviceID() const;
};
static_assert(sizeof(CTCert) == 0x180, "Invalid CTCert size.");
// Title ID valid length // Title ID valid length
constexpr std::size_t TITLE_ID_VALID_LENGTH = 16; constexpr std::size_t TITLE_ID_VALID_LENGTH = 16;
@ -208,6 +233,7 @@ Result UninstallProgram(const FS::MediaType media_type, const u64 title_id);
class Module final { class Module final {
public: public:
Module();
explicit Module(Core::System& system); explicit Module(Core::System& system);
~Module(); ~Module();
@ -216,6 +242,10 @@ public:
Interface(std::shared_ptr<Module> am, const char* name, u32 max_session); Interface(std::shared_ptr<Module> am, const char* name, u32 max_session);
~Interface(); ~Interface();
std::shared_ptr<Module> GetModule() const {
return am;
}
protected: protected:
/** /**
* AM::GetNumPrograms service function * AM::GetNumPrograms service function
@ -415,6 +445,16 @@ public:
*/ */
void GetTicketList(Kernel::HLERequestContext& ctx); void GetTicketList(Kernel::HLERequestContext& ctx);
/**
* AM::GetDeviceID service function
* Inputs:
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Unknown
* 3 : DeviceID
*/
void GetDeviceID(Kernel::HLERequestContext& ctx);
/** /**
* AM::NeedsCleanup service function * AM::NeedsCleanup service function
* Inputs: * Inputs:
@ -702,10 +742,37 @@ public:
*/ */
void EndImportTicket(Kernel::HLERequestContext& ctx); void EndImportTicket(Kernel::HLERequestContext& ctx);
/**
* AM::GetDeviceCert service function
* Inputs:
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Unknown
* 3-4 : Device cert
*/
void GetDeviceCert(Kernel::HLERequestContext& ctx);
protected: protected:
std::shared_ptr<Module> am; std::shared_ptr<Module> am;
}; };
/**
* Gets the CTCert.bin path in the host filesystem
* @returns std::string CTCert.bin path in the host filesystem
*/
std::string GetCTCertPath();
/**
* Invalidates the CTCert data so that it is loaded again.
*/
void InvalidateCTCertData();
/**
* Loads the CTCert.bin file from the filesystem.
* @returns CTCertLoadStatus indicating the file load status.
*/
CTCertLoadStatus LoadCTCertFile();
private: private:
/** /**
* Scans the for titles in a storage medium for listing. * Scans the for titles in a storage medium for listing.
@ -722,12 +789,15 @@ private:
bool cia_installing = false; bool cia_installing = false;
std::array<std::vector<u64_le>, 3> am_title_list; std::array<std::vector<u64_le>, 3> am_title_list;
std::shared_ptr<Kernel::Mutex> system_updater_mutex; std::shared_ptr<Kernel::Mutex> system_updater_mutex;
CTCert ct_cert{};
template <class Archive> template <class Archive>
void serialize(Archive& ar, const unsigned int); void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access; friend class boost::serialization::access;
}; };
std::shared_ptr<Module> GetModule(Core::System& system);
void InstallInterfaces(Core::System& system); void InstallInterfaces(Core::System& system);
} // namespace Service::AM } // namespace Service::AM

View file

@ -19,7 +19,7 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x0007, &AM_NET::DeleteTicket, "DeleteTicket"}, {0x0007, &AM_NET::DeleteTicket, "DeleteTicket"},
{0x0008, &AM_NET::GetNumTickets, "GetNumTickets"}, {0x0008, &AM_NET::GetNumTickets, "GetNumTickets"},
{0x0009, &AM_NET::GetTicketList, "GetTicketList"}, {0x0009, &AM_NET::GetTicketList, "GetTicketList"},
{0x000A, nullptr, "GetDeviceID"}, {0x000A, &AM_NET::GetDeviceID, "GetDeviceID"},
{0x000B, nullptr, "GetNumImportTitleContexts"}, {0x000B, nullptr, "GetNumImportTitleContexts"},
{0x000C, nullptr, "GetImportTitleContextList"}, {0x000C, nullptr, "GetImportTitleContextList"},
{0x000D, nullptr, "GetImportTitleContexts"}, {0x000D, nullptr, "GetImportTitleContexts"},
@ -103,7 +103,7 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x0815, nullptr, "GetCurrentImportContentContexts"}, {0x0815, nullptr, "GetCurrentImportContentContexts"},
{0x0816, nullptr, "Sign"}, {0x0816, nullptr, "Sign"},
{0x0817, nullptr, "Verify"}, {0x0817, nullptr, "Verify"},
{0x0818, nullptr, "GetDeviceCert"}, {0x0818, &AM_NET::GetDeviceCert, "GetDeviceCert"},
{0x0819, nullptr, "ImportCertificates"}, {0x0819, nullptr, "ImportCertificates"},
{0x081A, nullptr, "ImportCertificate"}, {0x081A, nullptr, "ImportCertificate"},
{0x081B, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"}, {0x081B, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"},

View file

@ -19,7 +19,7 @@ AM_SYS::AM_SYS(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x0007, &AM_SYS::DeleteTicket, "DeleteTicket"}, {0x0007, &AM_SYS::DeleteTicket, "DeleteTicket"},
{0x0008, &AM_SYS::GetNumTickets, "GetNumTickets"}, {0x0008, &AM_SYS::GetNumTickets, "GetNumTickets"},
{0x0009, &AM_SYS::GetTicketList, "GetTicketList"}, {0x0009, &AM_SYS::GetTicketList, "GetTicketList"},
{0x000A, nullptr, "GetDeviceID"}, {0x000A, &AM_SYS::GetDeviceID, "GetDeviceID"},
{0x000B, nullptr, "GetNumImportTitleContexts"}, {0x000B, nullptr, "GetNumImportTitleContexts"},
{0x000C, nullptr, "GetImportTitleContextList"}, {0x000C, nullptr, "GetImportTitleContextList"},
{0x000D, nullptr, "GetImportTitleContexts"}, {0x000D, nullptr, "GetImportTitleContexts"},

View file

@ -19,7 +19,7 @@ AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u"
{0x0007, &AM_U::DeleteTicket, "DeleteTicket"}, {0x0007, &AM_U::DeleteTicket, "DeleteTicket"},
{0x0008, &AM_U::GetNumTickets, "GetNumTickets"}, {0x0008, &AM_U::GetNumTickets, "GetNumTickets"},
{0x0009, &AM_U::GetTicketList, "GetTicketList"}, {0x0009, &AM_U::GetTicketList, "GetTicketList"},
{0x000A, nullptr, "GetDeviceID"}, {0x000A, &AM_U::GetDeviceCert, "GetDeviceID"},
{0x000B, nullptr, "GetNumImportTitleContexts"}, {0x000B, nullptr, "GetNumImportTitleContexts"},
{0x000C, nullptr, "GetImportTitleContextList"}, {0x000C, nullptr, "GetImportTitleContextList"},
{0x000D, nullptr, "GetImportTitleContexts"}, {0x000D, nullptr, "GetImportTitleContexts"},