core: Add CMAKE option to disable built-in keyblob (#2024)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-libretro / android (push) Waiting to run
citra-libretro / linux (push) Waiting to run
citra-libretro / windows (push) Waiting to run
citra-libretro / macos (arm64) (push) Waiting to run
citra-libretro / macos (x86_64) (push) Waiting to run
citra-libretro / ios (push) Waiting to run
citra-libretro / tvos (push) Waiting to run
citra-transifex / transifex (push) Waiting to run

* core: Add CMAKE option to disable built-in keyblob

* Additional assurance against accidental inclusion of default_keys.h

* default_keys.h: Make default_keys_enc constexpr

* default_keys.h: Make default_keys_enc_size constexpr

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
This commit is contained in:
PabloMK7 2026-04-12 19:57:55 +02:00 committed by GitHub
parent e8c75b4107
commit 336d871a7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 75 additions and 14 deletions

View file

@ -137,6 +137,8 @@ option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON)
option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF) option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF)
option(ENABLE_BUILTIN_KEYBLOB "Enable the inclusion of the default crypto keys blob" ON)
# Compile options # Compile options
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF) CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO}) option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})

View file

@ -183,6 +183,9 @@ endif()
if(ENABLE_DEVELOPER_OPTIONS) if(ENABLE_DEVELOPER_OPTIONS)
add_compile_definitions(ENABLE_DEVELOPER_OPTIONS) add_compile_definitions(ENABLE_DEVELOPER_OPTIONS)
endif() endif()
if(ENABLE_BUILTIN_KEYBLOB)
add_compile_definitions(ENABLE_BUILTIN_KEYBLOB)
endif()
add_subdirectory(common) add_subdirectory(common)
add_subdirectory(core) add_subdirectory(core)

View file

@ -670,12 +670,16 @@ void ConfigureSystem::RefreshSecureDataStatus() {
return tr("Status: Loaded (Invalid Signature)"); return tr("Status: Loaded (Invalid Signature)");
case HW::UniqueData::SecureDataLoadStatus::RegionChanged: case HW::UniqueData::SecureDataLoadStatus::RegionChanged:
return tr("Status: Loaded (Region Changed)"); return tr("Status: Loaded (Region Changed)");
case HW::UniqueData::SecureDataLoadStatus::CannotValidateSignature:
return tr("Status: Loaded (Cannot Validate Signature)");
case HW::UniqueData::SecureDataLoadStatus::NotFound: case HW::UniqueData::SecureDataLoadStatus::NotFound:
return tr("Status: Not Found"); return tr("Status: Not Found");
case HW::UniqueData::SecureDataLoadStatus::Invalid: case HW::UniqueData::SecureDataLoadStatus::Invalid:
return tr("Status: Invalid"); return tr("Status: Invalid");
case HW::UniqueData::SecureDataLoadStatus::IOError: case HW::UniqueData::SecureDataLoadStatus::IOError:
return tr("Status: IO Error"); return tr("Status: IO Error");
case HW::UniqueData::SecureDataLoadStatus::NoCryptoKeys:
return tr("Status: Missing Crypto Keys");
default: default:
return QString(); return QString();
} }

View file

@ -464,7 +464,6 @@ add_library(citra_core STATIC
hw/aes/key.h hw/aes/key.h
hw/ecc.cpp hw/ecc.cpp
hw/ecc.h hw/ecc.h
hw/default_keys.h
hw/rsa/rsa.cpp hw/rsa/rsa.cpp
hw/rsa/rsa.h hw/rsa/rsa.h
hw/unique_data.cpp hw/unique_data.cpp
@ -502,6 +501,10 @@ add_library(citra_core STATIC
tracer/recorder.h tracer/recorder.h
) )
if (ENABLE_BUILTIN_KEYBLOB)
target_sources(citra_core PRIVATE hw/default_keys.h)
endif()
create_target_directory_groups(citra_core) create_target_directory_groups(citra_core)
target_link_libraries(citra_core PUBLIC citra_common PRIVATE audio_core network video_core) target_link_libraries(citra_core PUBLIC citra_common PRIVATE audio_core network video_core)

View file

@ -18,7 +18,9 @@
#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/archive.h"
#include "core/hw/aes/arithmetic128.h" #include "core/hw/aes/arithmetic128.h"
#include "core/hw/aes/key.h" #include "core/hw/aes/key.h"
#ifdef ENABLE_BUILTIN_KEYBLOB
#include "core/hw/default_keys.h" #include "core/hw/default_keys.h"
#endif // ENABLE_BUILTIN_KEYBLOB
#include "core/hw/rsa/rsa.h" #include "core/hw/rsa/rsa.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
@ -130,8 +132,8 @@ std::array<std::optional<AESKey>, NumDlpNfcKeyYs> dlp_nfc_key_y_slots;
std::array<NfcSecret, NumNfcSecrets> nfc_secrets; std::array<NfcSecret, NumNfcSecrets> nfc_secrets;
AESIV nfc_iv; AESIV nfc_iv;
AESKey otp_key; AESKey otp_key{};
AESIV otp_iv; AESIV otp_iv{};
// gets xor'd with the mac address to produce the final iv // gets xor'd with the mac address to produce the final iv
AESIV dlp_checksum_mod_iv; AESIV dlp_checksum_mod_iv;
@ -297,6 +299,7 @@ std::istringstream GetKeysStream() {
if (file.is_open()) { if (file.is_open()) {
return std::istringstream(std::string(std::istreambuf_iterator<char>(file), {})); return std::istringstream(std::string(std::istreambuf_iterator<char>(file), {}));
} else { } else {
#ifdef ENABLE_BUILTIN_KEYBLOB
// The key data is encrypted in the source to prevent easy access to it for unintended // The key data is encrypted in the source to prevent easy access to it for unintended
// purposes. // purposes.
std::vector<u8> kiv(16); std::vector<u8> kiv(16);
@ -304,6 +307,9 @@ std::istringstream GetKeysStream() {
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption(kiv.data(), kiv.size(), kiv.data()) CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption(kiv.data(), kiv.size(), kiv.data())
.ProcessData(reinterpret_cast<u8*>(s.data()), default_keys_enc, s.size()); .ProcessData(reinterpret_cast<u8*>(s.data()), default_keys_enc, s.size());
return std::istringstream(s); return std::istringstream(s);
#else
return std::istringstream("");
#endif // ENABLE_BUILTIN_KEYBLOB
} }
} }

View file

@ -2,7 +2,13 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
unsigned char default_keys_enc[] = { #pragma once
#ifndef ENABLE_BUILTIN_KEYBLOB
#error Attempting to include default_keys.h, but ENABLE_BUILTIN_KEYBLOB is disabled.
#endif
constexpr unsigned char default_keys_enc[] = {
0x4E, 0x81, 0xE9, 0x54, 0xCC, 0xDE, 0xFD, 0x56, 0x7D, 0xD2, 0x72, 0xE6, 0xD9, 0xCD, 0x8E, 0x11, 0x4E, 0x81, 0xE9, 0x54, 0xCC, 0xDE, 0xFD, 0x56, 0x7D, 0xD2, 0x72, 0xE6, 0xD9, 0xCD, 0x8E, 0x11,
0xE1, 0x7F, 0x74, 0xF4, 0xFC, 0x54, 0xA6, 0xA4, 0x27, 0xC2, 0xD7, 0x50, 0xEA, 0xE7, 0xBE, 0xC9, 0xE1, 0x7F, 0x74, 0xF4, 0xFC, 0x54, 0xA6, 0xA4, 0x27, 0xC2, 0xD7, 0x50, 0xEA, 0xE7, 0xBE, 0xC9,
0xA7, 0x5E, 0xE0, 0x2E, 0x4A, 0xBE, 0xF5, 0xD5, 0x0D, 0x22, 0x76, 0x2E, 0xB6, 0x80, 0xD8, 0x54, 0xA7, 0x5E, 0xE0, 0x2E, 0x4A, 0xBE, 0xF5, 0xD5, 0x0D, 0x22, 0x76, 0x2E, 0xB6, 0x80, 0xD8, 0x54,
@ -468,4 +474,4 @@ unsigned char default_keys_enc[] = {
0x14, 0x79, 0xD0, 0xA8, 0x3C, 0xB3, 0x46, 0xC3, 0xDA, 0x6C, 0x0C, 0xEC, 0x2A, 0xB2, 0x9B, 0x21, 0x14, 0x79, 0xD0, 0xA8, 0x3C, 0xB3, 0x46, 0xC3, 0xDA, 0x6C, 0x0C, 0xEC, 0x2A, 0xB2, 0x9B, 0x21,
0xB2, 0xAD, 0x8C, 0x0C, 0x85, 0x9A, 0x8D, 0x7C, 0x10, 0xEA, 0x51, 0x1D, 0x2D, 0xDE, 0x7D, 0x8F}; 0xB2, 0xAD, 0x8C, 0x0C, 0x85, 0x9A, 0x8D, 0x7C, 0x10, 0xEA, 0x51, 0x1D, 0x2D, 0xDE, 0x7D, 0x8F};
const long int default_keys_enc_size = sizeof(default_keys_enc); constexpr long int default_keys_enc_size = sizeof(default_keys_enc);

View file

@ -1,4 +1,4 @@
// Copyright 2020 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -14,7 +14,8 @@ class RsaSlot {
public: public:
RsaSlot() = default; RsaSlot() = default;
RsaSlot(std::vector<u8> exponent, std::vector<u8> modulus) RsaSlot(std::vector<u8> exponent, std::vector<u8> modulus)
: init(true), exponent(std::move(exponent)), modulus(std::move(modulus)) {} : init_exponent(true), init_modulus(true), exponent(std::move(exponent)),
modulus(std::move(modulus)) {}
std::vector<u8> ModularExponentiation(std::span<const u8> message, std::vector<u8> ModularExponentiation(std::span<const u8> message,
int out_size_bytes = -1) const; int out_size_bytes = -1) const;
@ -25,11 +26,12 @@ public:
explicit operator bool() const { explicit operator bool() const {
// TODO(B3N30): Maybe check if exponent and modulus are vailid // TODO(B3N30): Maybe check if exponent and modulus are vailid
return init; return init_exponent && init_modulus;
} }
void SetExponent(const std::vector<u8>& e) { void SetExponent(const std::vector<u8>& e) {
exponent = e; exponent = e;
init_exponent = true;
} }
const std::vector<u8>& GetExponent() const { const std::vector<u8>& GetExponent() const {
@ -38,6 +40,7 @@ public:
void SetModulus(const std::vector<u8>& m) { void SetModulus(const std::vector<u8>& m) {
modulus = m; modulus = m;
init_modulus = true;
} }
const std::vector<u8>& GetModulus() const { const std::vector<u8>& GetModulus() const {
@ -46,6 +49,7 @@ public:
void SetPrivateD(const std::vector<u8>& d) { void SetPrivateD(const std::vector<u8>& d) {
private_d = d; private_d = d;
init_private_d = true;
} }
const std::vector<u8>& GetPrivateD() const { const std::vector<u8>& GetPrivateD() const {
@ -53,7 +57,9 @@ public:
} }
private: private:
bool init = false; bool init_exponent = false;
bool init_modulus = false;
bool init_private_d = false;
std::vector<u8> exponent; std::vector<u8> exponent;
std::vector<u8> modulus; std::vector<u8> modulus;
std::vector<u8> private_d; std::vector<u8> private_d;

View file

@ -27,13 +27,17 @@ static MovableSedFull movable;
static bool movable_signature_valid = false; static bool movable_signature_valid = false;
bool SecureInfoA::VerifySignature() const { bool SecureInfoA::VerifySignature() const {
return HW::RSA::GetSecureInfoSlot().Verify( auto sec_info_slot = HW::RSA::GetSecureInfoSlot();
std::span<const u8>(reinterpret_cast<const u8*>(&body), sizeof(body)), signature); return sec_info_slot &&
sec_info_slot.Verify(
std::span<const u8>(reinterpret_cast<const u8*>(&body), sizeof(body)), signature);
} }
bool LocalFriendCodeSeedB::VerifySignature() const { bool LocalFriendCodeSeedB::VerifySignature() const {
return HW::RSA::GetLocalFriendCodeSeedSlot().Verify( auto lfcs_slot = HW::RSA::GetLocalFriendCodeSeedSlot();
std::span<const u8>(reinterpret_cast<const u8*>(&body), sizeof(body)), signature); return lfcs_slot &&
HW::RSA::GetLocalFriendCodeSeedSlot().Verify(
std::span<const u8>(reinterpret_cast<const u8*>(&body), sizeof(body)), signature);
} }
bool MovableSed::VerifySignature() const { bool MovableSed::VerifySignature() const {
@ -42,6 +46,9 @@ bool MovableSed::VerifySignature() const {
SecureDataLoadStatus LoadSecureInfoA() { SecureDataLoadStatus LoadSecureInfoA() {
if (secure_info_a.IsValid()) { if (secure_info_a.IsValid()) {
if (!HW::RSA::GetSecureInfoSlot()) {
return SecureDataLoadStatus::CannotValidateSignature;
}
return secure_info_a_signature_valid return secure_info_a_signature_valid
? SecureDataLoadStatus::Loaded ? SecureDataLoadStatus::Loaded
: (secure_info_a_region_changed ? SecureDataLoadStatus::RegionChanged : (secure_info_a_region_changed ? SecureDataLoadStatus::RegionChanged
@ -63,8 +70,11 @@ SecureDataLoadStatus LoadSecureInfoA() {
return SecureDataLoadStatus::IOError; return SecureDataLoadStatus::IOError;
} }
HW::AES::InitKeys();
secure_info_a_region_changed = false; secure_info_a_region_changed = false;
HW::AES::InitKeys();
if (!HW::RSA::GetSecureInfoSlot()) {
return SecureDataLoadStatus::CannotValidateSignature;
}
secure_info_a_signature_valid = secure_info_a.VerifySignature(); secure_info_a_signature_valid = secure_info_a.VerifySignature();
if (!secure_info_a_signature_valid) { if (!secure_info_a_signature_valid) {
// Check if the file has been region changed // Check if the file has been region changed
@ -93,6 +103,9 @@ SecureDataLoadStatus LoadSecureInfoA() {
SecureDataLoadStatus LoadLocalFriendCodeSeedB() { SecureDataLoadStatus LoadLocalFriendCodeSeedB() {
if (local_friend_code_seed_b.IsValid()) { if (local_friend_code_seed_b.IsValid()) {
if (!HW::RSA::GetLocalFriendCodeSeedSlot()) {
return SecureDataLoadStatus::CannotValidateSignature;
}
return local_friend_code_seed_b_signature_valid ? SecureDataLoadStatus::Loaded return local_friend_code_seed_b_signature_valid ? SecureDataLoadStatus::Loaded
: SecureDataLoadStatus::InvalidSignature; : SecureDataLoadStatus::InvalidSignature;
} }
@ -114,6 +127,9 @@ SecureDataLoadStatus LoadLocalFriendCodeSeedB() {
} }
HW::AES::InitKeys(); HW::AES::InitKeys();
if (!HW::RSA::GetLocalFriendCodeSeedSlot()) {
return SecureDataLoadStatus::CannotValidateSignature;
}
local_friend_code_seed_b_signature_valid = local_friend_code_seed_b.VerifySignature(); local_friend_code_seed_b_signature_valid = local_friend_code_seed_b.VerifySignature();
if (!local_friend_code_seed_b_signature_valid) { if (!local_friend_code_seed_b_signature_valid) {
LOG_WARNING(HW, "LocalFriendCodeSeed_B signature check failed"); LOG_WARNING(HW, "LocalFriendCodeSeed_B signature check failed");
@ -128,10 +144,17 @@ SecureDataLoadStatus LoadOTP() {
return SecureDataLoadStatus::Loaded; return SecureDataLoadStatus::Loaded;
} }
auto is_all_zero = [](const auto& arr) {
return std::all_of(arr.begin(), arr.end(), [](auto x) { return x == 0; });
};
const std::string filepath = GetOTPPath(); const std::string filepath = GetOTPPath();
HW::AES::InitKeys(); HW::AES::InitKeys();
auto otp_keyiv = HW::AES::GetOTPKeyIV(); auto otp_keyiv = HW::AES::GetOTPKeyIV();
if (is_all_zero(otp_keyiv.first) || is_all_zero(otp_keyiv.second)) {
return SecureDataLoadStatus::NoCryptoKeys;
}
auto loader_status = otp.Load(filepath, otp_keyiv.first, otp_keyiv.second); auto loader_status = otp.Load(filepath, otp_keyiv.first, otp_keyiv.second);
if (loader_status != Loader::ResultStatus::Success) { if (loader_status != Loader::ResultStatus::Success) {
@ -169,6 +192,9 @@ SecureDataLoadStatus LoadOTP() {
SecureDataLoadStatus LoadMovable() { SecureDataLoadStatus LoadMovable() {
if (movable.IsValid()) { if (movable.IsValid()) {
if (!HW::RSA::GetLocalFriendCodeSeedSlot()) {
return SecureDataLoadStatus::CannotValidateSignature;
}
return movable_signature_valid ? SecureDataLoadStatus::Loaded return movable_signature_valid ? SecureDataLoadStatus::Loaded
: SecureDataLoadStatus::InvalidSignature; : SecureDataLoadStatus::InvalidSignature;
} }
@ -193,6 +219,9 @@ SecureDataLoadStatus LoadMovable() {
} }
HW::AES::InitKeys(); HW::AES::InitKeys();
if (!HW::RSA::GetLocalFriendCodeSeedSlot()) {
return SecureDataLoadStatus::CannotValidateSignature;
}
movable_signature_valid = movable.VerifySignature(); movable_signature_valid = movable.VerifySignature();
if (!movable_signature_valid) { if (!movable_signature_valid) {
LOG_WARNING(HW, "movable.sed signature check failed"); LOG_WARNING(HW, "movable.sed signature check failed");

View file

@ -136,10 +136,12 @@ enum class SecureDataLoadStatus {
Loaded = 0, Loaded = 0,
InvalidSignature = 1, InvalidSignature = 1,
RegionChanged = 2, RegionChanged = 2,
CannotValidateSignature = 3,
NotFound = -1, NotFound = -1,
Invalid = -2, Invalid = -2,
IOError = -3, IOError = -3,
NoCryptoKeys = -4,
}; };
SecureDataLoadStatus LoadSecureInfoA(); SecureDataLoadStatus LoadSecureInfoA();