feat(hardening): 内嵌产品配置 + 系统级状态 + 全量 i18n + 依赖治理 + 资料清理

update-client-hardening 四工作流合入(构建/静态检查已过):

- 配置:不可变产品策略经 UpdateClientResources.cmake 归一化校验后
  内嵌 qrc(URL/相对路径/主程序必须落 install_root/版本/UUID 格式,
  CMake override 写入前复验);可变机器状态迁系统级原生存储;
  运行目录不再落可编辑 app_config.json。
- i18n:生产源全英文(no-Han 检查 41/41),中文进 translations/
  zh_CN 目录;翻译加载收敛 Common/TranslationHelper 唯一入口;
  Bootstrap 补资源文件。
- 依赖:Qt/OpenSSL/架构/Perl 机器路径改 imported targets +
  UpdateClientDependencies.cmake,3rdparty 由父工程契约供给。
- 清理:旧 README/SDK README/3 个重复 PowerShell 打包脚本/PDF 提取
  文本删除,canonical README + 5 份结构化英文文档替代;Launcher
  requireAdministrator manifest;统一 MSVC /utf-8 删运行时编码设置。
This commit is contained in:
Comely
2026-07-10 00:38:23 -07:00
parent b06e003502
commit fe5aa5bbf0
45 changed files with 5033 additions and 4083 deletions
+46 -6
View File
@@ -22,12 +22,35 @@ bool PolicyHelper::loadPolicy(const QString& relativePath)
if (error.error != QJsonParseError::NoError || !doc.isObject()) {
m_error = "Invalid policy JSON: " + error.errorString(); return false;
}
return loadPolicyObject(doc.object());
const QJsonObject stored = doc.object();
if (stored.value("policy").isObject() && stored.value("policy_text").isString())
return loadPolicyObject(stored.value("policy").toObject(),
stored.value("policy_text").toString());
return loadPolicyObject(stored);
}
bool PolicyHelper::loadPolicyObject(const QJsonObject& policy, const QString& signedText)
{
m_policy = policy; m_signedText = signedText; m_loaded = true; m_error.clear();
m_error.clear();
m_signedText = signedText;
m_policy = policy;
if (!signedText.isEmpty())
{
QJsonParseError parseError;
const QJsonDocument signedDocument =
QJsonDocument::fromJson(signedText.toUtf8(), &parseError);
if (parseError.error != QJsonParseError::NoError || !signedDocument.isObject())
{
m_error = "Signed policy text is invalid JSON: " + parseError.errorString();
m_loaded = false;
return false;
}
const QString signature = policy.value("signature").toString();
m_policy = signedDocument.object();
m_policy.insert("signature", signature);
}
m_loaded = true;
return isValid();
}
@@ -35,7 +58,12 @@ bool PolicyHelper::savePolicy(const QString& relativePath) const
{
QSaveFile file(m_baseDir + "/" + relativePath);
if (!file.open(QIODevice::WriteOnly)) return false;
const QByteArray bytes = QJsonDocument(m_policy).toJson(QJsonDocument::Indented);
QJsonObject stored = m_policy;
if (!m_signedText.isEmpty())
{
stored = QJsonObject{{"policy", m_policy}, {"policy_text", m_signedText}};
}
const QByteArray bytes = QJsonDocument(stored).toJson(QJsonDocument::Indented);
return file.write(bytes) == bytes.size() && file.commit();
}
@@ -68,9 +96,8 @@ bool PolicyHelper::verifySignature(const QByteArray& payload, const QString& sig
#ifndef HAVE_OPENSSL
Q_UNUSED(payload); Q_UNUSED(signatureBase64); m_error = "OpenSSL unavailable"; return false;
#else
QString keyPath = m_baseDir + "/config/manifest_public_key.pem";
QFile keyFile(keyPath);
if (!keyFile.open(QIODevice::ReadOnly)) { m_error = "Cannot open policy public key"; return false; }
QFile keyFile(QStringLiteral(":/simcae/update-client/manifest-public-key.pem"));
if (!keyFile.open(QIODevice::ReadOnly)) { m_error = "Cannot open embedded policy public key"; return false; }
const QByteArray keyData = keyFile.readAll();
BIO* bio = BIO_new_mem_buf(keyData.constData(), keyData.size());
EVP_PKEY* key = bio ? PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr) : nullptr;
@@ -97,6 +124,19 @@ bool PolicyHelper::isValid() const
const QByteArray payload = m_signedText.isEmpty() ? canonicalPolicyBytes(m_policy) : m_signedText.toUtf8();
return verifySignature(payload, m_policy.value("signature").toString());
}
bool PolicyHelper::isApplicable(const QString& appId, const QString& channel,
const QString& currentVersion) const
{
if (!isValid()) return false;
if (m_policy.value("app_id").toString() != appId
|| m_policy.value("channel").toString() != channel
|| m_policy.value("current_version").toString() != currentVersion)
{
m_error = "Policy identity does not match this installation";
return false;
}
return true;
}
QString PolicyHelper::errorString() const { return m_error; }
bool PolicyHelper::isVersionAllowed(const QString& version) const {
if (!isValid()) return false;