Add full implementation: core engine, UI, build fixes, and compilation
- Implement all core modules: disk I/O, partition tables, filesystem formatting, recovery, imaging, diagnostics, security, and maintenance - Implement all UI tabs with full widget layouts and backend integration - Fix MSVC compilation: NOMINMAX, WIN32_LEAN_AND_MEAN, missing includes (winioctl.h, bcrypt.h, shellapi.h, cwctype), type mismatches, and POSIX macro conflicts - Add Guid implementation (Types.cpp), move DiskAccessMode to Types.h - Add CMake presets with embedded MSVC/SDK environment for Git Bash builds - Add build scripts, key generation, icon resources, and windeployqt - Include pre-built hwdiag library and third-party integration
This commit is contained in:
63
tools/bootstrap_encrypt.cmake
Normal file
63
tools/bootstrap_encrypt.cmake
Normal file
@@ -0,0 +1,63 @@
|
||||
# bootstrap_encrypt.cmake — First-time encryption of secret sources.
|
||||
#
|
||||
# Run this ONCE after cloning to generate the .enc files from plaintext:
|
||||
# cmake -P tools/bootstrap_encrypt.cmake
|
||||
#
|
||||
# After this, commit the .enc files and the plaintext sources will be
|
||||
# gitignored. Subsequent builds only use the .enc files.
|
||||
#
|
||||
# Prerequisites: build spw_src_cipher first:
|
||||
# cmake --preset default && cmake --build build/default --target spw_src_cipher
|
||||
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
|
||||
set(PASSPHRASE "SQ-1.0.0-WilcoAlpha7")
|
||||
set(CIPHER_TOOL "${CMAKE_CURRENT_LIST_DIR}/../build/default/tools/spw_src_cipher")
|
||||
set(ENC_DIR "${CMAKE_CURRENT_LIST_DIR}/../src/ui/tabs/encrypted_src")
|
||||
|
||||
# Check tool exists
|
||||
if(NOT EXISTS "${CIPHER_TOOL}" AND NOT EXISTS "${CIPHER_TOOL}.exe")
|
||||
message(FATAL_ERROR
|
||||
"spw_src_cipher not found. Build it first:\n"
|
||||
" cmake --preset default\n"
|
||||
" cmake --build build/default --target spw_src_cipher")
|
||||
endif()
|
||||
|
||||
# Fix extension on Windows
|
||||
if(EXISTS "${CIPHER_TOOL}.exe")
|
||||
set(CIPHER_TOOL "${CIPHER_TOOL}.exe")
|
||||
endif()
|
||||
|
||||
set(FILES_TO_ENCRYPT
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../src/ui/tabs/StarGenerator.cpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../src/ui/tabs/StarGenerator.h"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../src/ui/dialogs/AstroChicken.cpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../src/ui/dialogs/AstroChicken.h"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../src/ui/dialogs/Vohaul.cpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../src/ui/dialogs/Vohaul.h"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../src/ui/dialogs/Arnoid.cpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../src/ui/dialogs/Arnoid.h"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../src/core/security/OratDecoder.cpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../src/core/security/OratDecoder.h"
|
||||
)
|
||||
|
||||
file(MAKE_DIRECTORY "${ENC_DIR}")
|
||||
|
||||
foreach(SRC_FILE ${FILES_TO_ENCRYPT})
|
||||
get_filename_component(BASENAME "${SRC_FILE}" NAME)
|
||||
set(ENC_FILE "${ENC_DIR}/${BASENAME}.enc")
|
||||
|
||||
message(STATUS "Encrypting ${BASENAME} -> ${BASENAME}.enc")
|
||||
execute_process(
|
||||
COMMAND "${CIPHER_TOOL}" encrypt "${PASSPHRASE}" "${SRC_FILE}" "${ENC_FILE}"
|
||||
RESULT_VARIABLE RESULT
|
||||
)
|
||||
if(NOT RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Failed to encrypt ${BASENAME}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
message(STATUS "")
|
||||
message(STATUS "All secret sources encrypted to ${ENC_DIR}/")
|
||||
message(STATUS "You can now commit the .enc files and remove plaintext from git.")
|
||||
message(STATUS "The plaintext files are gitignored and will not be tracked.")
|
||||
206
tools/keygen.cpp
Normal file
206
tools/keygen.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
// Build-time 1337-bit cryptographic key generator for Setec Partition Wizard.
|
||||
// Generates a 1337-bit key using OS CSPRNG and outputs:
|
||||
// 1. A C++ header with the embedded key
|
||||
// 2. An encrypted garbage.xtx file
|
||||
//
|
||||
// The 1337-bit (168-byte, with the top bit of the last byte masked) key is used
|
||||
// with a cascaded cipher: Salsa20-variant XOR stream derived from the key via
|
||||
// repeated SHA-256-like mixing, applied to the plaintext "Roger Wilco Was Here."
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <bcrypt.h>
|
||||
#pragma comment(lib, "bcrypt.lib")
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static const char* PLAINTEXT = "Roger Wilco Was Here.";
|
||||
static constexpr int KEY_BITS = 1337;
|
||||
static constexpr int KEY_BYTES = (KEY_BITS + 7) / 8; // 168 bytes
|
||||
|
||||
// Fill buffer with cryptographically secure random bytes
|
||||
static bool csprng_fill(uint8_t* buf, size_t len)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
NTSTATUS status = BCryptGenRandom(nullptr, buf, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
return status == 0;
|
||||
#else
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
ssize_t n = read(fd, buf, len);
|
||||
close(fd);
|
||||
return n == (ssize_t)len;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Simple but effective mixing function (SipHash-inspired round)
|
||||
static void mix_round(uint8_t* state, size_t len, uint8_t round_key)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
state[i] ^= round_key;
|
||||
state[i] = (state[i] << 3) | (state[i] >> 5);
|
||||
state[i] += state[(i + 7) % len];
|
||||
state[i] ^= state[(i + 13) % len];
|
||||
}
|
||||
}
|
||||
|
||||
// Derive a keystream from the master key using cascaded mixing
|
||||
static std::vector<uint8_t> derive_keystream(const uint8_t* key, size_t key_len, size_t stream_len)
|
||||
{
|
||||
// Initialize state from key
|
||||
std::vector<uint8_t> state(key, key + key_len);
|
||||
|
||||
// Expand state to needed length
|
||||
while (state.size() < stream_len + 64)
|
||||
{
|
||||
size_t old_size = state.size();
|
||||
state.resize(old_size + key_len);
|
||||
for (size_t i = 0; i < key_len; i++)
|
||||
{
|
||||
state[old_size + i] = key[i] ^ (uint8_t)(old_size + i);
|
||||
}
|
||||
}
|
||||
|
||||
// 256 rounds of mixing
|
||||
for (int round = 0; round < 256; round++)
|
||||
{
|
||||
mix_round(state.data(), state.size(), (uint8_t)round ^ key[round % key_len]);
|
||||
}
|
||||
|
||||
return std::vector<uint8_t>(state.begin(), state.begin() + stream_len);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if (argc != 3)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <output_header.h> <output_garbage.xtx>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* header_path = argv[1];
|
||||
const char* xtx_path = argv[2];
|
||||
|
||||
// Generate 1337-bit key
|
||||
uint8_t key[KEY_BYTES] = {};
|
||||
if (!csprng_fill(key, KEY_BYTES))
|
||||
{
|
||||
fprintf(stderr, "ERROR: Failed to generate cryptographic random bytes\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Mask the top bit to exactly 1337 bits (1337 = 167*8 + 1, so bit 0 of byte 167)
|
||||
// 1337 bits = 167 full bytes + 1 bit. Mask upper 7 bits of last byte.
|
||||
key[KEY_BYTES - 1] &= 0x01;
|
||||
|
||||
// Derive keystream for encryption
|
||||
size_t plaintext_len = strlen(PLAINTEXT);
|
||||
auto keystream = derive_keystream(key, KEY_BYTES, plaintext_len + 32);
|
||||
|
||||
// Encrypt plaintext
|
||||
std::vector<uint8_t> ciphertext(plaintext_len);
|
||||
for (size_t i = 0; i < plaintext_len; i++)
|
||||
{
|
||||
ciphertext[i] = (uint8_t)PLAINTEXT[i] ^ keystream[i];
|
||||
}
|
||||
|
||||
// Generate 32-byte verification tag (from remaining keystream XOR'd with plaintext hash)
|
||||
uint8_t tag[32] = {};
|
||||
uint8_t plaintext_hash = 0;
|
||||
for (size_t i = 0; i < plaintext_len; i++)
|
||||
{
|
||||
plaintext_hash ^= (uint8_t)PLAINTEXT[i];
|
||||
plaintext_hash = (plaintext_hash << 1) | (plaintext_hash >> 7);
|
||||
}
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
tag[i] = keystream[plaintext_len + i] ^ plaintext_hash ^ (uint8_t)i;
|
||||
}
|
||||
|
||||
// Write C++ header with embedded key
|
||||
{
|
||||
std::ofstream hdr(header_path);
|
||||
if (!hdr)
|
||||
{
|
||||
fprintf(stderr, "ERROR: Cannot write header to %s\n", header_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
hdr << "#pragma once\n";
|
||||
hdr << "// AUTO-GENERATED — DO NOT EDIT\n";
|
||||
hdr << "// 1337-bit cryptographic key generated at build time\n";
|
||||
hdr << "#include <cstdint>\n";
|
||||
hdr << "#include <cstddef>\n\n";
|
||||
hdr << "namespace spw { namespace internal {\n\n";
|
||||
hdr << "static constexpr size_t kKeyBits = " << KEY_BITS << ";\n";
|
||||
hdr << "static constexpr size_t kKeyBytes = " << KEY_BYTES << ";\n\n";
|
||||
hdr << "static constexpr uint8_t kMasterKey[" << KEY_BYTES << "] = {\n ";
|
||||
|
||||
for (int i = 0; i < KEY_BYTES; i++)
|
||||
{
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof(buf), "0x%02X", key[i]);
|
||||
hdr << buf;
|
||||
if (i < KEY_BYTES - 1)
|
||||
hdr << ", ";
|
||||
if ((i + 1) % 16 == 0 && i < KEY_BYTES - 1)
|
||||
hdr << "\n ";
|
||||
}
|
||||
hdr << "\n};\n\n";
|
||||
|
||||
// Also embed expected ciphertext length for validation
|
||||
hdr << "static constexpr size_t kPayloadLen = " << plaintext_len << ";\n";
|
||||
hdr << "static constexpr size_t kTagLen = 32;\n\n";
|
||||
|
||||
hdr << "}} // namespace spw::internal\n";
|
||||
}
|
||||
|
||||
// Write garbage.xtx: [ciphertext][tag]
|
||||
// The file looks like random garbage in a hex editor, but a text editor
|
||||
// will show "Roger Wilco Was Here." because we prepend the plaintext
|
||||
// followed by null bytes and the encrypted blob.
|
||||
//
|
||||
// Actually, per the requirement: "If they open it in a text editor it says
|
||||
// 'Roger Wilco Was Here.'" — so the plaintext IS visible. The key validates
|
||||
// authenticity (that it wasn't tampered with), not secrecy.
|
||||
{
|
||||
std::ofstream xtx(xtx_path, std::ios::binary);
|
||||
if (!xtx)
|
||||
{
|
||||
fprintf(stderr, "ERROR: Cannot write garbage.xtx to %s\n", xtx_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Plaintext (visible in text editor)
|
||||
xtx.write(PLAINTEXT, plaintext_len);
|
||||
|
||||
// Separator (null + magic marker)
|
||||
const uint8_t sep[] = {0x00, 0x13, 0x37, 0xBE, 0xEF};
|
||||
xtx.write(reinterpret_cast<const char*>(sep), sizeof(sep));
|
||||
|
||||
// Encrypted blob (ciphertext)
|
||||
xtx.write(reinterpret_cast<const char*>(ciphertext.data()), ciphertext.size());
|
||||
|
||||
// Verification tag
|
||||
xtx.write(reinterpret_cast<const char*>(tag), sizeof(tag));
|
||||
}
|
||||
|
||||
printf("Generated %d-bit key -> %s\n", KEY_BITS, header_path);
|
||||
printf("Generated garbage.xtx -> %s (%zu bytes)\n", xtx_path,
|
||||
plaintext_len + 5 + ciphertext.size() + 32);
|
||||
|
||||
return 0;
|
||||
}
|
||||
216
tools/src_cipher.cpp
Normal file
216
tools/src_cipher.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
// Source file encryption/decryption tool for Setec Partition Wizard.
|
||||
//
|
||||
// Encrypts C++ source files so they cannot be read from the repo or filesystem.
|
||||
// Only the build system (which knows the key) can decrypt them for compilation.
|
||||
//
|
||||
// Usage:
|
||||
// src_cipher encrypt <key> <input_file> <output_file.enc>
|
||||
// src_cipher decrypt <key> <input_file.enc> <output_file>
|
||||
//
|
||||
// Encryption: XOR stream cipher with 256-round cascaded key derivation.
|
||||
// File format: [8-byte magic "SPWSRC01"][4-byte original size][encrypted data][32-byte tag]
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static constexpr char MAGIC[] = "SPWSRC01";
|
||||
static constexpr size_t MAGIC_LEN = 8;
|
||||
static constexpr size_t TAG_LEN = 32;
|
||||
|
||||
static void mix_round(uint8_t* state, size_t len, uint8_t round_key)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
state[i] ^= round_key;
|
||||
state[i] = (state[i] << 3) | (state[i] >> 5);
|
||||
state[i] += state[(i + 7) % len];
|
||||
state[i] ^= state[(i + 13) % len];
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> derive_keystream(const std::string& passphrase, size_t stream_len)
|
||||
{
|
||||
// Derive key bytes from passphrase
|
||||
std::vector<uint8_t> key(passphrase.begin(), passphrase.end());
|
||||
|
||||
// Ensure minimum key length
|
||||
while (key.size() < 64)
|
||||
{
|
||||
size_t old = key.size();
|
||||
key.resize(old + passphrase.size());
|
||||
for (size_t i = 0; i < passphrase.size(); i++)
|
||||
key[old + i] = passphrase[i] ^ (uint8_t)(old + i) ^ 0xC3;
|
||||
}
|
||||
|
||||
// Expand to stream length
|
||||
std::vector<uint8_t> state = key;
|
||||
while (state.size() < stream_len + 64)
|
||||
{
|
||||
size_t old = state.size();
|
||||
state.resize(old + key.size());
|
||||
for (size_t i = 0; i < key.size(); i++)
|
||||
state[old + i] = key[i] ^ (uint8_t)(old + i);
|
||||
}
|
||||
|
||||
// 256 rounds of cascaded mixing
|
||||
for (int round = 0; round < 256; round++)
|
||||
{
|
||||
mix_round(state.data(), state.size(), (uint8_t)round ^ key[round % key.size()]);
|
||||
}
|
||||
|
||||
return std::vector<uint8_t>(state.begin(), state.begin() + stream_len);
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> compute_tag(const uint8_t* data, size_t len, const std::string& passphrase)
|
||||
{
|
||||
auto stream = derive_keystream(passphrase + "_tag_verify", TAG_LEN + len);
|
||||
std::vector<uint8_t> tag(TAG_LEN);
|
||||
uint8_t acc = 0;
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
acc ^= data[i];
|
||||
acc = (acc << 1) | (acc >> 7);
|
||||
}
|
||||
for (size_t i = 0; i < TAG_LEN; i++)
|
||||
{
|
||||
tag[i] = stream[i] ^ acc ^ (uint8_t)i;
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
static int do_encrypt(const std::string& key, const std::string& inpath, const std::string& outpath)
|
||||
{
|
||||
// Read input
|
||||
std::ifstream in(inpath, std::ios::binary);
|
||||
if (!in)
|
||||
{
|
||||
fprintf(stderr, "Cannot open input: %s\n", inpath.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::vector<uint8_t> plaintext((std::istreambuf_iterator<char>(in)),
|
||||
std::istreambuf_iterator<char>());
|
||||
in.close();
|
||||
|
||||
// Derive keystream
|
||||
auto keystream = derive_keystream(key, plaintext.size());
|
||||
|
||||
// Encrypt
|
||||
std::vector<uint8_t> ciphertext(plaintext.size());
|
||||
for (size_t i = 0; i < plaintext.size(); i++)
|
||||
ciphertext[i] = plaintext[i] ^ keystream[i];
|
||||
|
||||
// Compute tag over ciphertext
|
||||
auto tag = compute_tag(ciphertext.data(), ciphertext.size(), key);
|
||||
|
||||
// Write output: magic + size + ciphertext + tag
|
||||
std::ofstream out(outpath, std::ios::binary);
|
||||
if (!out)
|
||||
{
|
||||
fprintf(stderr, "Cannot open output: %s\n", outpath.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t orig_size = (uint32_t)plaintext.size();
|
||||
out.write(MAGIC, MAGIC_LEN);
|
||||
out.write(reinterpret_cast<const char*>(&orig_size), 4);
|
||||
out.write(reinterpret_cast<const char*>(ciphertext.data()), ciphertext.size());
|
||||
out.write(reinterpret_cast<const char*>(tag.data()), TAG_LEN);
|
||||
|
||||
printf("Encrypted %s -> %s (%zu -> %zu bytes)\n",
|
||||
inpath.c_str(), outpath.c_str(), plaintext.size(),
|
||||
MAGIC_LEN + 4 + ciphertext.size() + TAG_LEN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_decrypt(const std::string& key, const std::string& inpath, const std::string& outpath)
|
||||
{
|
||||
std::ifstream in(inpath, std::ios::binary);
|
||||
if (!in)
|
||||
{
|
||||
fprintf(stderr, "Cannot open input: %s\n", inpath.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::vector<uint8_t> raw((std::istreambuf_iterator<char>(in)),
|
||||
std::istreambuf_iterator<char>());
|
||||
in.close();
|
||||
|
||||
// Validate minimum size and magic
|
||||
if (raw.size() < MAGIC_LEN + 4 + TAG_LEN)
|
||||
{
|
||||
fprintf(stderr, "File too small or corrupt\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (memcmp(raw.data(), MAGIC, MAGIC_LEN) != 0)
|
||||
{
|
||||
fprintf(stderr, "Invalid file magic\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t orig_size = 0;
|
||||
memcpy(&orig_size, raw.data() + MAGIC_LEN, 4);
|
||||
|
||||
size_t cipher_offset = MAGIC_LEN + 4;
|
||||
size_t cipher_len = raw.size() - MAGIC_LEN - 4 - TAG_LEN;
|
||||
|
||||
if (cipher_len != orig_size)
|
||||
{
|
||||
fprintf(stderr, "Size mismatch: expected %u, got %zu\n", orig_size, cipher_len);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const uint8_t* ciphertext = raw.data() + cipher_offset;
|
||||
const uint8_t* file_tag = raw.data() + cipher_offset + cipher_len;
|
||||
|
||||
// Verify tag
|
||||
auto expected_tag = compute_tag(ciphertext, cipher_len, key);
|
||||
if (memcmp(file_tag, expected_tag.data(), TAG_LEN) != 0)
|
||||
{
|
||||
fprintf(stderr, "Tag verification failed — wrong key or corrupt file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Decrypt
|
||||
auto keystream = derive_keystream(key, cipher_len);
|
||||
std::vector<uint8_t> plaintext(cipher_len);
|
||||
for (size_t i = 0; i < cipher_len; i++)
|
||||
plaintext[i] = ciphertext[i] ^ keystream[i];
|
||||
|
||||
// Write output
|
||||
std::ofstream out(outpath, std::ios::binary);
|
||||
if (!out)
|
||||
{
|
||||
fprintf(stderr, "Cannot open output: %s\n", outpath.c_str());
|
||||
return 1;
|
||||
}
|
||||
out.write(reinterpret_cast<const char*>(plaintext.data()), plaintext.size());
|
||||
|
||||
printf("Decrypted %s -> %s (%zu bytes)\n", inpath.c_str(), outpath.c_str(), plaintext.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if (argc != 5)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <encrypt|decrypt> <key> <input> <output>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string mode = argv[1];
|
||||
std::string key = argv[2];
|
||||
std::string inpath = argv[3];
|
||||
std::string outpath = argv[4];
|
||||
|
||||
if (mode == "encrypt")
|
||||
return do_encrypt(key, inpath, outpath);
|
||||
else if (mode == "decrypt")
|
||||
return do_decrypt(key, inpath, outpath);
|
||||
|
||||
fprintf(stderr, "Unknown mode: %s (use 'encrypt' or 'decrypt')\n", mode.c_str());
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user