security(exposure): owner-only ACLs on Windows, auto-clearing export copy, require SHA-256 for bootstrap
- writeFileAtomically honored restrictPermissions only on POSIX (0600); on Windows it was a no-op, so exported keys / seed backups / the vault / chat exports inherited the parent dir's ACL. It now builds a PROTECTED owner-only DACL (current-user SID, SE_DACL_PROTECTED) on the temp file before the atomic rename; best-effort, falling back to default ACLs on failure. (Verified it compiles under mingw-w64; the POSIX path is unchanged.) - The key-export dialog's Copy wrote the private key to the plain, permanent clipboard; it now goes through copySecretToClipboard() (45s armed auto-wipe + its own "copied, auto-clears" notice), like the seed backup copy. - Bootstrap archive verification dropped the collision-prone MD5 fallback: it now requires a published SHA-256 and fails closed otherwise. (Authenticity vs a malicious server still needs a detached ed25519 signature like the updaters — a separate release-coordinated change.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+22
-45
@@ -1125,56 +1125,33 @@ bool Bootstrap::verifyChecksums(const std::string& zipPath, const std::string& b
|
||||
std::string expectedSha = parseChecksumFile(sha256Content, 64);
|
||||
const bool haveSHA256 = !expectedSha.empty();
|
||||
|
||||
std::string expectedMd5;
|
||||
bool haveMD5 = false;
|
||||
if (!haveSHA256) { // MD5 only as a fallback when no SHA-256 is published
|
||||
std::string md5Content = httpGetString(baseUrl + "/" + kZipName + ".md5", "[bootstrap]", opt);
|
||||
if (cancel_requested_) return false;
|
||||
expectedMd5 = parseChecksumFile(md5Content, 32);
|
||||
haveMD5 = !expectedMd5.empty();
|
||||
}
|
||||
|
||||
if (!haveSHA256 && !haveMD5) {
|
||||
DEBUG_LOGF("[Bootstrap] No checksum published - refusing to install an unverified archive\n");
|
||||
setProgress(State::Failed, "Verification failed: no checksum was published for the bootstrap. "
|
||||
// SHA-256 is now required: the collision-prone MD5 fallback was removed. (A same-origin checksum
|
||||
// only protects against transport corruption, not a malicious server — true authenticity needs a
|
||||
// detached ed25519 signature like the daemon/xmrig updaters; that is a separate release-coordinated
|
||||
// change, since it requires publishing per-archive .sig files and pinning a key.)
|
||||
if (!haveSHA256) {
|
||||
DEBUG_LOGF("[Bootstrap] No SHA-256 published - refusing to install an unverified archive\n");
|
||||
setProgress(State::Failed, "Verification failed: no SHA-256 checksum was published for the bootstrap. "
|
||||
"Refusing to install an unverified archive.");
|
||||
return false; // fail CLOSED; archive is KEPT
|
||||
}
|
||||
|
||||
if (haveSHA256) {
|
||||
setProgress(State::Verifying, "Verifying SHA-256...", 0, 1);
|
||||
std::string actual = computeSHA256(zipPath, 0.0f, 100.0f);
|
||||
if (cancel_requested_) return false;
|
||||
if (actual.empty()) { setProgress(State::Failed, "SHA-256 verification error"); return false; }
|
||||
if (expectedSha != actual) {
|
||||
last_verify_mismatch_ = true;
|
||||
DEBUG_LOGF("[Bootstrap] SHA-256 MISMATCH!\n expected: %s\n actual: %s\n",
|
||||
expectedSha.c_str(), actual.c_str());
|
||||
setProgress(State::Failed,
|
||||
"SHA-256 mismatch \xe2\x80\x94 the server's checksum file may be out of date.\n"
|
||||
"Expected: " + expectedSha.substr(0, 16) + "...\n"
|
||||
"Got: " + actual.substr(0, 16) + "...\n"
|
||||
"Try again or use a fresh download.");
|
||||
return false;
|
||||
}
|
||||
DEBUG_LOGF("[Bootstrap] SHA-256 verified: %s\n", actual.c_str());
|
||||
} else { // haveMD5
|
||||
setProgress(State::Verifying, "Verifying MD5...", 0, 1);
|
||||
std::string actual = computeMD5(zipPath, 0.0f, 100.0f);
|
||||
if (cancel_requested_) return false;
|
||||
if (actual.empty()) { setProgress(State::Failed, "MD5 verification error"); return false; }
|
||||
if (expectedMd5 != actual) {
|
||||
last_verify_mismatch_ = true;
|
||||
DEBUG_LOGF("[Bootstrap] MD5 MISMATCH!\n expected: %s\n actual: %s\n",
|
||||
expectedMd5.c_str(), actual.c_str());
|
||||
setProgress(State::Failed,
|
||||
"MD5 mismatch \xe2\x80\x94 the server's checksum file may be out of date.\n"
|
||||
"Expected: " + expectedMd5 + "\nGot: " + actual + "\n"
|
||||
"Try again or use a fresh download.");
|
||||
return false;
|
||||
}
|
||||
DEBUG_LOGF("[Bootstrap] MD5 verified: %s\n", actual.c_str());
|
||||
setProgress(State::Verifying, "Verifying SHA-256...", 0, 1);
|
||||
std::string actual = computeSHA256(zipPath, 0.0f, 100.0f);
|
||||
if (cancel_requested_) return false;
|
||||
if (actual.empty()) { setProgress(State::Failed, "SHA-256 verification error"); return false; }
|
||||
if (expectedSha != actual) {
|
||||
last_verify_mismatch_ = true;
|
||||
DEBUG_LOGF("[Bootstrap] SHA-256 MISMATCH!\n expected: %s\n actual: %s\n",
|
||||
expectedSha.c_str(), actual.c_str());
|
||||
setProgress(State::Failed,
|
||||
"SHA-256 mismatch \xe2\x80\x94 the server's checksum file may be out of date.\n"
|
||||
"Expected: " + expectedSha.substr(0, 16) + "...\n"
|
||||
"Got: " + actual.substr(0, 16) + "...\n"
|
||||
"Try again or use a fresh download.");
|
||||
return false;
|
||||
}
|
||||
DEBUG_LOGF("[Bootstrap] SHA-256 verified: %s\n", actual.c_str());
|
||||
|
||||
setProgress(State::Verifying, "Checksums verified \xe2\x9c\x93", 100.0, 100.0);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user