Add optional SHA-256 verification of downloaded manifest pieces

Each Sony manifest piece carries a SHA-256 (hashValue). When the new
"Verify downloaded pieces" setting is on, every piece is hashed while it
streams to disk (OpenSSL EVP, already linked) and compared against the
manifest value; a mismatch aborts the download, deletes the partial, and
reports piece_verify_failed instead of handing a corrupt 60 GB package to
the installer.

Off by default: TLS already protects the bytes in transit and the PS5
installer verifies the whole packageDigest before applying, so this is a
fail-fast belt-and-suspenders check. It is also unverified on hardware
yet, so it stays opt-in (persisted in config.json) until confirmed
on-device; the hex compare is case-insensitive since Sony mixes cases.
This commit is contained in:
Knutwurst committed 2026-06-23 18:08:57 +02:00
1 parent 9006965a75
commit ea1328de79
5 files changed
+117 -27

No files matched your search

+6
View File
@@ -47,6 +47,7 @@ const fallback = {
download_dir: "/data/patchdl (internal)",
install_after_download: false,
delete_pkg_after_install: true,
verify_downloads: false,
source_policy: {
official: { allow_check: true, allow_download: true, allow_install: true },
external: { allow_check: true, allow_download: true, allow_install: true },
@@ -168,6 +169,7 @@ function bindElements() {
downloadDir: document.getElementById("downloadDir"),
installAfterDownload: document.getElementById("installAfterDownload"),
deleteAfterInstall: document.getElementById("deleteAfterInstall"),
verifyDownloads: document.getElementById("verifyDownloads"),
refreshBtn: document.getElementById("refreshBtn"),
saveBtn: document.getElementById("saveBtn"),
pauseAllBtn: document.getElementById("pauseAllBtn"),
@@ -262,6 +264,8 @@ function renderSettings() {
els.downloadDir.value = state.config.download_dir || "";
els.installAfterDownload.checked = Boolean(state.config.install_after_download);
els.deleteAfterInstall.checked = Boolean(state.config.delete_pkg_after_install);
if (els.verifyDownloads)
els.verifyDownloads.checked = Boolean(state.config.verify_downloads);
els.allowlistHosts.replaceChildren(...(state.config.cdn_allowlist || []).map((host) => {
const chip = document.createElement("span");
chip.className = "host-chip";
@@ -573,6 +577,7 @@ async function saveConfig() {
download_dir: els.downloadDir.value.trim(),
install_after_download: els.installAfterDownload.checked,
delete_pkg_after_install: els.deleteAfterInstall.checked,
verify_downloads: els.verifyDownloads ? els.verifyDownloads.checked : Boolean(state.config.verify_downloads),
};
try {
@@ -782,6 +787,7 @@ const REASON_TEXT = {
no_compatible_patch: "No compatible patch available.",
download_failed: "Download failed.",
download_in_progress: "Another download is already running.",
piece_verify_failed: "A downloaded piece failed its SHA-256 check.",
};
function reasonText(error) {
const r = error && error.body && error.body.reason;
+7
View File
@@ -184,6 +184,13 @@
<em>Only after a successful install</em>
</span>
</label>
<label class="switch-row">
<input id="verifyDownloads" type="checkbox" />
<span>
<strong>Verify downloaded pieces (SHA-256)</strong>
<em>Checks each manifest piece; off by default, verify on-device first</em>
</span>
</label>
</div>
<div class="allowlist">