forked from Public/monero-gui
updater: fetch signed hashes from getmonero.org, verify downloads
This commit is contained in:
@@ -31,6 +31,8 @@
|
||||
#include <QReadLocker>
|
||||
#include <QWriteLocker>
|
||||
|
||||
#include "updater.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -112,10 +114,10 @@ void Downloader::cancel()
|
||||
m_contents.clear();
|
||||
}
|
||||
|
||||
bool Downloader::get(const QString &url, const QJSValue &callback)
|
||||
bool Downloader::get(const QString &url, const QString &hash, const QJSValue &callback)
|
||||
{
|
||||
auto future = m_scheduler.run(
|
||||
[this, url]() {
|
||||
[this, url, hash]() {
|
||||
DownloaderStateGuard stateGuard(m_active, m_mutex, [this]() {
|
||||
emit activeChanged();
|
||||
});
|
||||
@@ -153,6 +155,19 @@ bool Downloader::get(const QString &url, const QJSValue &callback)
|
||||
return QJSValueList({"empty response"});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const QByteArray calculatedHash = Updater().getHash(&response[0], response.size());
|
||||
if (QByteArray::fromHex(hash.toUtf8()) != calculatedHash)
|
||||
{
|
||||
return QJSValueList({"hash sum mismatch"});
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
return QJSValueList({e.what()});
|
||||
}
|
||||
|
||||
{
|
||||
QWriteLocker locker(&m_mutex);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
~Downloader();
|
||||
|
||||
Q_INVOKABLE void cancel();
|
||||
Q_INVOKABLE bool get(const QString &url, const QJSValue &callback);
|
||||
Q_INVOKABLE bool get(const QString &url, const QString &hash, const QJSValue &callback);
|
||||
Q_INVOKABLE bool saveToFile(const QString &path) const;
|
||||
|
||||
signals:
|
||||
|
||||
@@ -117,6 +117,17 @@ void Network::getJSON(const QString &url, const QJSValue &callback) const
|
||||
get(url, callback, "application/json; charset=utf-8");
|
||||
}
|
||||
|
||||
std::string Network::get(const QString &url, const QString &contentType /* = {} */) const
|
||||
{
|
||||
std::string response;
|
||||
QString error = get(std::shared_ptr<http_simple_client>(new http_simple_client()), url, response, contentType);
|
||||
if (!error.isEmpty())
|
||||
{
|
||||
throw std::runtime_error(QString("failed to fetch %1: %2").arg(url).arg(error).toStdString());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
QString Network::get(
|
||||
std::shared_ptr<http_simple_client> httpClient,
|
||||
const QString &url,
|
||||
|
||||
@@ -77,6 +77,7 @@ public:
|
||||
Q_INVOKABLE void get(const QString &url, const QJSValue &callback, const QString &contentType = {}) const;
|
||||
Q_INVOKABLE void getJSON(const QString &url, const QJSValue &callback) const;
|
||||
|
||||
std::string get(const QString &url, const QString &contentType = {}) const;
|
||||
QString get(
|
||||
std::shared_ptr<epee::net_utils::http::http_simple_client> httpClient,
|
||||
const QString &url,
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include <openpgp/hash.h>
|
||||
|
||||
#include "network.h"
|
||||
#include "utils.h"
|
||||
|
||||
Updater::Updater()
|
||||
@@ -39,17 +40,41 @@ Updater::Updater()
|
||||
m_maintainers.emplace_back(fileGetContents(":/monero/utils/gpg_keys/luigi1111.asc").toStdString());
|
||||
}
|
||||
|
||||
QPair<QString, QString> Updater::verifySignaturesAndHashSum(
|
||||
QByteArray Updater::fetchSignedHash(
|
||||
const QString &binaryFilename,
|
||||
const QByteArray &hashFromDns,
|
||||
QPair<QString, QString> &signers) const
|
||||
{
|
||||
static constexpr const char hashesTxtUrl[] = "https://web.getmonero.org/downloads/hashes.txt";
|
||||
static constexpr const char hashesTxtSigUrl[] = "https://web.getmonero.org/downloads/hashes.txt.sig";
|
||||
|
||||
const Network network;
|
||||
std::string hashesTxt = network.get(hashesTxtUrl);
|
||||
std::string hashesTxtSig = network.get(hashesTxtSigUrl);
|
||||
|
||||
const QByteArray signedHash = verifyParseSignedHahes(
|
||||
QByteArray(&hashesTxt[0], hashesTxt.size()),
|
||||
QByteArray(&hashesTxtSig[0], hashesTxtSig.size()),
|
||||
binaryFilename,
|
||||
signers);
|
||||
|
||||
if (signedHash != hashFromDns)
|
||||
{
|
||||
throw std::runtime_error("DNS hash mismatch");
|
||||
}
|
||||
|
||||
return signedHash;
|
||||
}
|
||||
|
||||
QByteArray Updater::verifyParseSignedHahes(
|
||||
const QByteArray &armoredSignedHashes,
|
||||
const QByteArray &secondDetachedSignature,
|
||||
const QString &binaryFilename,
|
||||
const void *binaryData,
|
||||
size_t binarySize) const
|
||||
QPair<QString, QString> &signers) const
|
||||
{
|
||||
QString firstSigner;
|
||||
const QString signedMessage = verifySignature(armoredSignedHashes, firstSigner);
|
||||
const QString signedMessage = verifySignature(armoredSignedHashes, signers.first);
|
||||
|
||||
QString secondSigner = verifySignature(
|
||||
signers.second = verifySignature(
|
||||
epee::span<const uint8_t>(
|
||||
reinterpret_cast<const uint8_t *>(armoredSignedHashes.data()),
|
||||
armoredSignedHashes.size()),
|
||||
@@ -57,19 +82,31 @@ QPair<QString, QString> Updater::verifySignaturesAndHashSum(
|
||||
reinterpret_cast<const uint8_t *>(secondDetachedSignature.data()),
|
||||
secondDetachedSignature.size())));
|
||||
|
||||
if (firstSigner == secondSigner)
|
||||
if (signers.first == signers.second)
|
||||
{
|
||||
throw std::runtime_error("both signatures were generated by the same person");
|
||||
}
|
||||
|
||||
const QByteArray signedHash = parseShasumOutput(signedMessage, binaryFilename);
|
||||
return parseShasumOutput(signedMessage, binaryFilename);
|
||||
}
|
||||
|
||||
QPair<QString, QString> Updater::verifySignaturesAndHashSum(
|
||||
const QByteArray &armoredSignedHashes,
|
||||
const QByteArray &secondDetachedSignature,
|
||||
const QString &binaryFilename,
|
||||
const void *binaryData,
|
||||
size_t binarySize) const
|
||||
{
|
||||
QPair<QString, QString> signers;
|
||||
const QByteArray signedHash =
|
||||
verifyParseSignedHahes(armoredSignedHashes, secondDetachedSignature, binaryFilename, signers);
|
||||
const QByteArray calculatedHash = getHash(binaryData, binarySize);
|
||||
if (signedHash != calculatedHash)
|
||||
{
|
||||
throw std::runtime_error("hash sum mismatch");
|
||||
}
|
||||
|
||||
return {firstSigner, secondSigner};
|
||||
return signers;
|
||||
}
|
||||
|
||||
QByteArray Updater::getHash(const void *data, size_t size) const
|
||||
|
||||
@@ -37,6 +37,11 @@ class Updater
|
||||
public:
|
||||
Updater();
|
||||
|
||||
QByteArray fetchSignedHash(
|
||||
const QString &binaryFilename,
|
||||
const QByteArray &hashFromDns,
|
||||
QPair<QString, QString> &signers) const;
|
||||
QByteArray getHash(const void *data, size_t size) const;
|
||||
QPair<QString, QString> verifySignaturesAndHashSum(
|
||||
const QByteArray &armoredSignedHashes,
|
||||
const QByteArray &secondDetachedSignature,
|
||||
@@ -45,7 +50,11 @@ public:
|
||||
size_t binarySize) const;
|
||||
|
||||
private:
|
||||
QByteArray getHash(const void *data, size_t size) const;
|
||||
QByteArray verifyParseSignedHahes(
|
||||
const QByteArray &armoredSignedHashes,
|
||||
const QByteArray &secondDetachedSignature,
|
||||
const QString &binaryFilename,
|
||||
QPair<QString, QString> &signers) const;
|
||||
QString verifySignature(const QByteArray &armoredSignedMessage, QString &signer) const;
|
||||
QString verifySignature(const epee::span<const uint8_t> data, const openpgp::signature_rsa &signature) const;
|
||||
QByteArray parseShasumOutput(const QString &message, const QString &filename) const;
|
||||
|
||||
Reference in New Issue
Block a user