Skip to content
This repository has been archived by the owner on Oct 29, 2024. It is now read-only.

Commit

Permalink
Add console unique data (SecureInfo, LocalFriendCodeSeed, CTCert) (#6)
Browse files Browse the repository at this point in the history
* Add console unique secure data

* Add CTCert and DeviceID support

* Fix AM_U::GetDeviceID

Co-authored-by: Daniel López Guimaraes <[email protected]>

* Update to latest master changes.

---------

Co-authored-by: Daniel López Guimaraes <[email protected]>
  • Loading branch information
PabloMK7 and DaniElectra authored Mar 5, 2024
1 parent f112d97 commit 813d0c2
Show file tree
Hide file tree
Showing 12 changed files with 597 additions and 14 deletions.
85 changes: 85 additions & 0 deletions src/citra_qt/configuration/configure_system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
// Refer to the license.txt file included.

#include <cstring>
#include <QFileDialog>
#include <QFutureWatcher>
#include <QMessageBox>
#include <QProgressDialog>
#include <QtConcurrent/QtConcurrentMap>
#include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_system.h"
#include "common/file_util.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/service/am/am.h"
Expand Down Expand Up @@ -236,6 +238,32 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
&ConfigureSystem::RefreshConsoleID);
connect(ui->button_start_download, &QPushButton::clicked, this,
&ConfigureSystem::DownloadFromNUS);

connect(ui->button_secure_info, &QPushButton::clicked, this, [this] {
ui->button_secure_info->setEnabled(false);
const QString file_path_qtstr = QFileDialog::getOpenFileName(
this, tr("Select SecureInfo_A/B"), QString(),
tr("SecureInfo_A/B (SecureInfo_A SecureInfo_B);;All Files (*.*)"));
ui->button_secure_info->setEnabled(true);
InstallSecureData(file_path_qtstr.toStdString(), cfg->GetSecureInfoAPath());
});
connect(ui->button_friend_code_seed, &QPushButton::clicked, this, [this] {
ui->button_friend_code_seed->setEnabled(false);
const QString file_path_qtstr =
QFileDialog::getOpenFileName(this, tr("Select LocalFriendCodeSeed_A/B"), QString(),
tr("LocalFriendCodeSeed_A/B (LocalFriendCodeSeed_A "
"LocalFriendCodeSeed_B);;All Files (*.*)"));
ui->button_friend_code_seed->setEnabled(true);
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++) {
if (std::strcmp(country_names.at(i), "") != 0) {
ui->combo_country->addItem(tr(country_names.at(i)), i);
Expand Down Expand Up @@ -304,6 +332,7 @@ void ConfigureSystem::SetConfiguration() {
ReadSystemSettings();

ui->group_system_settings->setEnabled(enabled);
ui->group_real_console_unique_data->setEnabled(enabled);
if (enabled) {
ui->label_disable_info->hide();
}
Expand Down Expand Up @@ -354,6 +383,9 @@ void ConfigureSystem::ReadSystemSettings() {

// set firmware download region
ui->combo_download_region->setCurrentIndex(static_cast<int>(cfg->GetRegionValue()));

// Refresh secure data status
RefreshSecureDataStatus();
}

void ConfigureSystem::ApplyConfiguration() {
Expand Down Expand Up @@ -522,6 +554,59 @@ void ConfigureSystem::RefreshConsoleID() {
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
}

void ConfigureSystem::InstallSecureData(const std::string& from_path, const std::string& to_path) {
std::string from =
FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault);
std::string to = FileUtil::SanitizePath(to_path, FileUtil::DirectorySeparator::PlatformDefault);
if (from.empty() || from == to) {
return;
}
FileUtil::CreateFullPath(to_path);
FileUtil::Copy(from, to);
cfg->InvalidateSecureData();
RefreshSecureDataStatus();
}

void ConfigureSystem::InstallCTCert(const std::string& from_path) {
std::string from =
FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault);
std::string to = FileUtil::SanitizePath(Service::AM::Module::GetCTCertPath(),
FileUtil::DirectorySeparator::PlatformDefault);
if (from.empty() || from == to) {
return;
}
FileUtil::Copy(from, to);
RefreshSecureDataStatus();
}

void ConfigureSystem::RefreshSecureDataStatus() {
auto status_to_str = [](Service::CFG::SecureDataLoadStatus status) {
switch (status) {
case Service::CFG::SecureDataLoadStatus::Loaded:
return "Loaded";
case Service::CFG::SecureDataLoadStatus::NotFound:
return "Not Found";
case Service::CFG::SecureDataLoadStatus::Invalid:
return "Invalid";
case Service::CFG::SecureDataLoadStatus::IOError:
return "IO Error";
default:
return "";
}
};

Service::AM::CTCert ct_cert;

ui->label_secure_info_status->setText(
tr((std::string("Status: ") + status_to_str(cfg->LoadSecureInfoAFile())).c_str()));
ui->label_friend_code_seed_status->setText(
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>(
Service::AM::Module::LoadCTCertFile(ct_cert))))
.c_str()));
}

void ConfigureSystem::RetranslateUI() {
ui->retranslateUi(this);
}
Expand Down
10 changes: 10 additions & 0 deletions src/citra_qt/configuration/configure_system.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ namespace Core {
class System;
}

namespace Service {
namespace AM {
class Module;
} // namespace AM
} // namespace Service

namespace Service {
namespace CFG {
class Module;
Expand All @@ -46,6 +52,10 @@ class ConfigureSystem : public QWidget {
void UpdateInitTicks(int init_ticks_type);
void RefreshConsoleID();

void InstallSecureData(const std::string& from_path, const std::string& to_path);
void InstallCTCert(const std::string& from_path);
void RefreshSecureDataStatus();

void SetupPerGameUI();

void DownloadFromNUS();
Expand Down
117 changes: 117 additions & 0 deletions src/citra_qt/configuration/configure_system.ui
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,123 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="group_real_console_unique_data">
<property name="title">
<string>Real Console Unique Data</string>
</property>
<layout class="QGridLayout" name="gridLayout1">
<item row="1" column="0">
<widget class="QLabel" name="label_secure_info">
<property name="text">
<string>SecureInfo_A/B</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QWidget" name="secure_info">
<layout class="QHBoxLayout" name="horizontalLayout_secure_info">
<item>
<widget class="QLabel" name="label_secure_info_status">
<property name="text">
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_secure_info">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Choose</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_friend_code_seed">
<property name="text">
<string>LocalFriendCodeSeed_A/B</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QWidget" name="friend_code_seed">
<layout class="QHBoxLayout" name="horizontalLayout_friend_code_seed">
<item>
<widget class="QLabel" name="label_friend_code_seed_status">
<property name="text">
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_friend_code_seed">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Choose</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_ct_cert">
<property name="text">
<string>CTCert</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QWidget" name="ct_cert">
<layout class="QHBoxLayout" name="horizontalLayout_ct_cert">
<item>
<widget class="QLabel" name="label_ct_cert_status">
<property name="text">
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_ct_cert">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Choose</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_disable_info">
<property name="text">
Expand Down
99 changes: 98 additions & 1 deletion src/core/hle/service/am/am.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,35 @@ struct TicketInfo {

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 {
public:
std::vector<CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption> content;
Expand Down Expand Up @@ -1213,6 +1242,21 @@ void Module::Interface::GetTicketList(Kernel::HLERequestContext& ctx) {
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(ResultSuccess);
rb.Push(0);
rb.Push(deviceID);
}

void Module::Interface::NeedsCleanup(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto media_type = rp.Pop<u8>();
Expand Down Expand Up @@ -1802,13 +1846,66 @@ void Module::serialize(Archive& ar, const unsigned int) {
}
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(ResultSuccess);
rb.Push(0);
rb.PushMappedBuffer(buffer);
}

std::string Module::GetCTCertPath() {
return FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + "CTCert.bin";
}

CTCertLoadStatus Module::LoadCTCertFile(CTCert& output) {
if (output.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(&output, sizeof(CTCert)) != sizeof(CTCert)) {
return CTCertLoadStatus::IOError;
}
if (!output.IsValid()) {
output = CTCert();
return CTCertLoadStatus::Invalid;
}
return CTCertLoadStatus::Loaded;
}

Module::Module(Core::System& _system) : system(_system) {
ScanForAllTitles();
LoadCTCertFile(ct_cert);
system_updater_mutex = system.Kernel().CreateMutex(false, "AM::SystemUpdaterMutex");
}

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) {
auto& service_manager = system.ServiceManager();
auto am = std::make_shared<Module>(system);
Expand Down
Loading

0 comments on commit 813d0c2

Please sign in to comment.