feat(mining): opt-in ed25519 signature verification for the xmrig updater (#1)
Closes the supply-chain gap the review flagged: today the archive and its SHA-256 share one trust root (the release body), so a compromised/edited release can ship an arbitrary binary that still "verifies". This adds authenticity via a detached ed25519 signature checked against a public key PINNED IN THE BINARY (not fetched), using libsodium's crypto_sign_verify_detached. Opt-in / soft rollout: - kXmrigSignaturePublicKeyBase64 in xmrig_updater.h is EMPTY by default -> signatures are not checked and behavior is unchanged (TLS + SHA-256 only). Paste the base64 public key to enable. - Once a key is pinned, an install verifies a "<archive>.sig" asset (base64/raw 64-byte ed25519 signature over the archive bytes) when present; kXmrigRequireSignature=true additionally refuses installs that publish no signature. - The check runs after the SHA-256 check, over the same already-read archive bytes; refuses on a missing key-but-required, unreachable .sig, or invalid signature. - verifyXmrigSignature + selectXmrigSignatureAsset are pure (libsodium only) and unit-tested: valid base64 + raw-64-byte signatures verify; tampered data, wrong key, and malformed/empty inputs all fail closed. Cross-tool interop verified (Python stdlib base64 == sodium base64). - scripts/sign-xmrig-release.sh: keygen / sign / pubkey helper (PyNaCl = same libsodium ed25519) to produce the .sig assets and the public key to pin. No behavior change until a key is pinned. Both variants build; suite passes; live worker re-verified (signatures off by default). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
|
||||
using json = nlohmann::json;
|
||||
@@ -144,5 +145,56 @@ std::vector<std::string> xmrigExtractBasenames(const std::string& platformToken)
|
||||
return {"xmrig"};
|
||||
}
|
||||
|
||||
int selectXmrigSignatureAsset(const XmrigRelease& release, const std::string& archiveName)
|
||||
{
|
||||
if (archiveName.empty()) return -1;
|
||||
for (std::size_t i = 0; i < release.assets.size(); ++i) {
|
||||
const std::string& n = release.assets[i].name;
|
||||
if (n == archiveName + ".sig" || n == archiveName + ".minisig")
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Decode base64 (whitespace tolerated) into exactly `expected` bytes; false on any mismatch.
|
||||
bool base64ToFixed(const std::string& b64, unsigned char* out, std::size_t expected)
|
||||
{
|
||||
std::size_t binLen = 0;
|
||||
const char* end = nullptr;
|
||||
if (sodium_base642bin(out, expected, b64.data(), b64.size(), " \t\r\n",
|
||||
&binLen, &end, sodium_base64_VARIANT_ORIGINAL) != 0)
|
||||
return false;
|
||||
return binLen == expected;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool verifyXmrigSignature(const std::string& data,
|
||||
const std::string& signatureFileContent,
|
||||
const std::string& pubKeyBase64)
|
||||
{
|
||||
if (sodium_init() < 0) return false;
|
||||
|
||||
unsigned char pubKey[crypto_sign_PUBLICKEYBYTES];
|
||||
if (!base64ToFixed(pubKeyBase64, pubKey, sizeof(pubKey))) return false;
|
||||
|
||||
// Trim the signature content; accept either base64 or a raw 64-byte signature.
|
||||
std::string sigText = signatureFileContent;
|
||||
while (!sigText.empty() &&
|
||||
(sigText.back() == '\n' || sigText.back() == '\r' ||
|
||||
sigText.back() == ' ' || sigText.back() == '\t'))
|
||||
sigText.pop_back();
|
||||
|
||||
unsigned char sig[crypto_sign_BYTES];
|
||||
if (sigText.size() == crypto_sign_BYTES) {
|
||||
std::memcpy(sig, sigText.data(), crypto_sign_BYTES);
|
||||
} else if (!base64ToFixed(sigText, sig, sizeof(sig))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return crypto_sign_verify_detached(
|
||||
sig, reinterpret_cast<const unsigned char*>(data.data()), data.size(), pubKey) == 0;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
|
||||
Reference in New Issue
Block a user